#include <QtGui>

#include "CodeEditor.h"
#include "CodeEditorPrivate.h"
#include "CodeDocumentLayout.h"
#include "FileDocument.h"
#include "CodeEditorConstants.h"
#include "CodeEditorOverlay.h"

using namespace CodeEdit;
using namespace CodeEdit::Internal;

//! @class CodeEditExtraArea
class CodeEditExtraArea : public QWidget
{
public:
    CodeEditExtraArea(CodeEditor* editor) : QWidget(editor) {
        _codeEditor = editor;
        setAutoFillBackground(true);
    }

    QSize sizeHint() const {
        return QSize(_codeEditor->extraAreaWidth(), 0);
    }

protected:
    virtual void paintEvent(QPaintEvent* event) {
        _codeEditor->extraAreaPaintEvent(event);
    }
    virtual void mousePressEvent(QMouseEvent *event) {
        _codeEditor->extraAreaMouseEvent(event);
    }
    virtual void mouseMoveEvent(QMouseEvent *event) {
        _codeEditor->extraAreaMouseEvent(event);
    }
    virtual void mouseReleaseEvent(QMouseEvent *event) {
        _codeEditor->extraAreaMouseEvent(event);
    }
    virtual void leaveEvent(QEvent *event) {
        _codeEditor->extraAreaLeaveEvent(event);
    }
    virtual void wheelEvent(QWheelEvent *event) {
        QCoreApplication::sendEvent(_codeEditor->viewport(), event);
    }

private:
    CodeEditor* _codeEditor;
};

using namespace CodeEdit;

//! @class CodeEditor
CodeEditor::CodeEditor(QWidget* parent) : QPlainTextEdit(parent)
{
    d = new CodeEditorPrivate;
    d->q = this;
    d->extraArea = new CodeEditExtraArea(this);
    d->extraArea->setMouseTracking(true);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    _bookmarkManager = new Bookmarks::BookmarkManager(this);

    d->setupDocumentSignals(d->document);

    setCursorWidth(2);

    setLayoutDirection(Qt::LeftToRight);
    viewport()->setMouseTracking(true);
    d->extraAreaSelectionAnchorBlockNumber
        = d->extraAreaToggleMarkBlockNumber
        = d->extraAreaHighlightFoldedBlockNumber
        = -1;

    d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber = -1;

    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(slotUpdateExtraAreaWidth()));
    connect(this, SIGNAL(updateRequest(QRect, int)), this, SLOT(slotUpdateRequest(QRect, int)));
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(slotCursorPositionChanged()));
    connect(this, SIGNAL(modificationChanged(bool)), this, SLOT(slotModificationChanged(bool)));
    connect(this, SIGNAL(selectionChanged()), this, SLOT(slotSelectionChanged()));
    connect(this, SIGNAL(markContextMenuRequested(int, QMenu*)),
            this, SLOT(requestMarkContextMenu(int, QMenu*)));
    connect(this, SIGNAL(markRequested(int)), this, SLOT(requestMark(int)));

    // parentheses matcher
    d->parenthesesMatchingEnabled = false;
    d->formatRange = true;
    d->matchFormat.setForeground(Qt::red);
    d->rangeFormat.setBackground(QColor(0xb4, 0xee, 0xb4));
    d->mismatchFormat.setBackground(Qt::magenta);

    d->parenthesesMatchingTimer = new QTimer(this);
    d->parenthesesMatchingTimer->setSingleShot(true);
    connect(d->parenthesesMatchingTimer, SIGNAL(timeout()), this, SLOT(matchParentheses()));

    d->highlightBlocksTimer = new QTimer(this);
    d->highlightBlocksTimer->setSingleShot(true);
    connect(d->highlightBlocksTimer, SIGNAL(timeout()), this, SLOT(highlightBlocks()));

    d->animator = 0;

    d->searchResultFormat.setBackground(QColor(0xffef0b));
    // end parentheses matcher

    slotUpdateExtraAreaWidth();
    slotCursorPositionChanged();
}

CodeEditor::~CodeEditor()
{
    delete d;
    d = 0;
}

void CodeEditor::setSyntaxHighlighter(QSyntaxHighlighter* highlighter)
{
    d->document->setSyntaxHighlighter(highlighter);
}

/**
 * Employed by createMimeDataFromSelection().
 */
static void convertToPlainText(QString &txt)
{
    QChar *uc = txt.data();
    QChar *e = uc + txt.size();

    for (; uc != e; ++uc) {
        switch (uc->unicode()) {
        case 0xfdd0: // QTextBeginningOfFrame
        case 0xfdd1: // QTextEndOfFrame
        case QChar::ParagraphSeparator:
        case QChar::LineSeparator:
            *uc = QLatin1Char('\n');
            break;
        case QChar::Nbsp:
            *uc = QLatin1Char(' ');
            break;
        default:
            ;
        }
    }
}

/**
 * Returns document's mime type.
 */
QString CodeEditor::mimeType() const
{
    return d->document->mimeType();
}

void CodeEditor::setMimeType(const QString &mt)
{
    d->document->setMimeType(mt);
}

/**
 * Pops up the print dialog to print the document.
 */
void CodeEditor::print(QPrinter *printer)
{
    const bool oldFullPage = printer->fullPage();
    printer->setFullPage(true);
    QPrintDialog *dlg = new QPrintDialog(printer, this);
    dlg->setWindowTitle(tr("Print Document"));
    if (dlg->exec() == QDialog::Accepted) {
        d->print(printer);
    }
    printer->setFullPage(oldFullPage);
    delete dlg;
}

/**
 * Returns foldbox's width.
 */
static int foldBoxWidth(const QFontMetrics &fm)
{
    const int lineSpacing = fm.lineSpacing();
    return lineSpacing + (lineSpacing % 2) + 1;
}

void CodeEditor::selectEncoding()
{
//+ todo
//    FileDocument *doc = d->document;
//    CodecSelector codecSelector(this, doc);
//
//    switch (codecSelector.exec()) {
//    case CodecSelector::Reload:
//        doc->reload(codecSelector.selectedCodec());
//        setReadOnly(d->m_document->hasDecodingError());
//        if (doc->hasDecodingError())
//            currentEditorChanged(Core::EditorManager::instance()->currentEditor());
//        else
//            Core::EditorManager::instance()->hideEditorInfoBar(QLatin1String(Constants::SELECT_ENCODING));
//        break;
//    case CodecSelector::Save:
//        doc->setCodec(codecSelector.selectedCodec());
//        Core::EditorManager::instance()->saveEditor(editableInterface());
//        break;
//    case CodecSelector::Cancel:
//        break;
//    }
}

bool CodeEditor::createNew(const QString &contents)
{
    setPlainText(contents);
    document()->setModified(false);
    return true;
}

bool CodeEditor::open(const QString &fileName)
{
    if (d->document->open(fileName)) {
        moveCursor(QTextCursor::Start);
        setReadOnly(d->document->hasDecodingError());
        return true;
    }
    return false;
}

bool CodeEditor::save(const QString &fileName)
{
    return d->document->save(fileName);
}

Core::IFile *CodeEditor::file()
{
    return d->document;
}

/**
 * @private, slot
 * On document()->contentsChange(int, int, int) signal.
 */
void CodeEditor::editorContentsChanged(int position, int charsRemoved, int charsAdded)
{
    if (d->animator)
        d->animator->finish();

    d->contentsChanged = true;
    QTextDocument *doc = document();

    // Keep the line numbers and the block information for the text marks updated
    if (charsRemoved != 0) {
        d->updateMarksLineNumber();
        d->updateMarksBlock(doc->findBlock(position));
    } else {
        const QTextBlock posBlock = doc->findBlock(position);
        const QTextBlock nextBlock = doc->findBlock(position + charsAdded);
        if (posBlock != nextBlock) {
            d->updateMarksLineNumber();
            d->updateMarksBlock(posBlock);
            d->updateMarksBlock(nextBlock);
        } else {
            d->updateMarksBlock(posBlock);
        }
    }

    if (doc->isRedoAvailable())
        emit contentsChangedBecauseOfUndo();
}

/**
 * @private, slot
 * On this->selectionChanged() signal.
 */
void CodeEditor::slotSelectionChanged()
{
    bool changed = (d->inBlockSelectionMode != d->lastEventWasBlockSelectionEvent);
    d->inBlockSelectionMode = d->lastEventWasBlockSelectionEvent;
    if (changed || d->inBlockSelectionMode)
        viewport()->update();
    if (!d->inBlockSelectionMode)
        d->blockSelectionExtraX = 0;
    if (!d->selectBlockAnchor.isNull() && !textCursor().hasSelection())
        d->selectBlockAnchor = QTextCursor();
}

/**
 * @public, slot
 * Goes to current block's start. Determined by parenthesis.
 */
void CodeEditor::gotoBlockStart(bool select)
{
    QTextCursor cursor = textCursor();
    if (TextBlockUserData::findPreviousOpenParenthesis(&cursor, select)) {
        setTextCursor(cursor);
        matchParentheses();
    }
}

/**
 * @public, slot
 * Goes to current block's end.
 */
void CodeEditor::gotoBlockEnd(bool select)
{
    QTextCursor cursor = textCursor();
    if (TextBlockUserData::findNextClosingParenthesis(&cursor, select)) {
        setTextCursor(cursor);
        matchParentheses();
    }
}

void CodeEditor::gotoLineStart(bool select)
{
//+    handleHomeKey(false);
}

void CodeEditor::gotoLineEnd(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::EndOfLine, mode);
}

void CodeEditor::gotoPreviousLine(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::PreviousRow, mode);
}

void CodeEditor::gotoNextLine(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::NextRow, mode);
}

void CodeEditor::gotoPreviousCharacter(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::PreviousCharacter, mode);
}

void CodeEditor::gotoNextCharacter(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::NextCharacter, mode);
}

void CodeEditor::gotoPreviousWord(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::PreviousWord, mode);
}

void CodeEditor::gotoNextWord(bool select)
{
    QTextCursor::MoveMode mode = select ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
    moveCursor(QTextCursor::NextWord, mode);
}

static QTextCursor flippedCursor(const QTextCursor &cursor)
{
    QTextCursor flipped = cursor;
    flipped.clearSelection();
    flipped.setPosition(cursor.anchor(), QTextCursor::KeepAnchor);
    return flipped;
}

void CodeEditor::selectBlockUp()
{
    QTextCursor cursor = textCursor();

    if (!cursor.hasSelection())
        d->selectBlockAnchor = cursor;
    else
        cursor.setPosition(cursor.selectionStart());

    if (!TextBlockUserData::findPreviousOpenParenthesis(&cursor, false))
        return;
    if (!TextBlockUserData::findNextClosingParenthesis(&cursor, true))
        return;

    setTextCursor(flippedCursor(cursor));
    matchParentheses();
}

void CodeEditor::selectBlockDown()
{
    QTextCursor tc = textCursor();
    QTextCursor cursor = d->selectBlockAnchor;

    if (!tc.hasSelection() || cursor.isNull())
        return;
    tc.setPosition(tc.selectionStart());

    forever {
        QTextCursor ahead = cursor;
        if (!TextBlockUserData::findPreviousOpenParenthesis(&ahead, false))
            break;
        if (ahead.position() <= tc.position())
            break;
        cursor = ahead;
    }
    if (cursor != d->selectBlockAnchor)
        TextBlockUserData::findNextClosingParenthesis(&cursor, true);

    setTextCursor(flippedCursor(cursor));
    matchParentheses();
}

void CodeEditor::copyLineUp()
{
    copyLineUpDown(true);
}

void CodeEditor::copyLineDown()
{
    copyLineUpDown(false);
}

void CodeEditor::copyLineUpDown(bool up)
{
    QTextCursor cursor = textCursor();
    QTextCursor move = cursor;
    move.beginEditBlock();

    bool hasSelection = cursor.hasSelection();

    if (hasSelection) {
        move.setPosition(cursor.selectionStart());
        move.movePosition(QTextCursor::StartOfBlock);
        move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
        move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    } else {
        move.movePosition(QTextCursor::StartOfBlock);
        move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    }

    QString text = move.selectedText();

    if (up) {
        move.setPosition(cursor.selectionStart());
        move.movePosition(QTextCursor::StartOfBlock);
        move.insertBlock();
        move.movePosition(QTextCursor::Left);
    } else {
        move.movePosition(QTextCursor::EndOfBlock);
        if (move.atBlockStart()) {
            move.movePosition(QTextCursor::NextBlock);
            move.insertBlock();
            move.movePosition(QTextCursor::Left);
        } else {
            move.insertBlock();
        }
    }

    int start = move.position();
    move.clearSelection();
    move.insertText(text);
    int end = move.position();

    move.setPosition(start);
    move.setPosition(end, QTextCursor::KeepAnchor);

    indent(document(), move, QChar::Null);
    move.endEditBlock();

    setTextCursor(move);
}

void CodeEditor::saveCurrentCursorPositionForNavigation()
{
    d->lastCursorChangeWasInteresting = true;
    d->tempNavigationState = saveState();
}

/**
 * @public, slot
 * Merges multiple lines as one line.
 */
void CodeEditor::joinLines()
{
    QTextCursor cursor = textCursor();
    QTextCursor start = cursor;
    QTextCursor end = cursor;

    start.setPosition(cursor.selectionStart());
    end.setPosition(cursor.selectionEnd() - 1);

    int lineCount = qMax(1, end.blockNumber() - start.blockNumber());

    cursor.beginEditBlock();
    cursor.setPosition(cursor.selectionStart());
    while (lineCount--) {
        cursor.movePosition(QTextCursor::NextBlock);
        cursor.movePosition(QTextCursor::StartOfBlock);
        cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
        QString cutLine = cursor.selectedText();

        // Collapse leading whitespaces to one or insert whitespace
        cutLine.replace(QRegExp("^\\s*"), " ");
        cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
        cursor.removeSelectedText();

        cursor.movePosition(QTextCursor::PreviousBlock);
        cursor.movePosition(QTextCursor::EndOfBlock);

        cursor.insertText(cutLine);
    }
    cursor.endEditBlock();

    setTextCursor(cursor);
}

void CodeEditor::insertLineAbove()
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::PreviousBlock);
    cursor.movePosition(QTextCursor::EndOfBlock);
    cursor.insertBlock();
    indent(document(), cursor, QChar::Null);
    cursor.endEditBlock();
    setTextCursor(cursor);
}

void CodeEditor::insertLineBelow()
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    cursor.movePosition(QTextCursor::EndOfBlock);
    cursor.insertBlock();
    indent(document(), cursor, QChar::Null);
    cursor.endEditBlock();
    setTextCursor(cursor);
}

void CodeEditor::moveLineUp()
{
    moveLineUpDown(true);
}

void CodeEditor::moveLineDown()
{
    moveLineUpDown(false);
}

void CodeEditor::moveLineUpDown(bool up)
{
    QTextCursor cursor = textCursor();
    QTextCursor move = cursor;

    move.setVisualNavigation(false); // this opens folded items instead of destroying them

    if (d->moveLineUndoHack)
        move.joinPreviousEditBlock();
    else
        move.beginEditBlock();

    bool hasSelection = cursor.hasSelection();

    if (hasSelection) {
        move.setPosition(cursor.selectionStart());
        move.movePosition(QTextCursor::StartOfBlock);
        move.setPosition(cursor.selectionEnd(), QTextCursor::KeepAnchor);
        move.movePosition(move.atBlockStart() ? QTextCursor::Left : QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    } else {
        move.movePosition(QTextCursor::StartOfBlock);
        move.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    }
    QString text = move.selectedText();
    move.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
    move.removeSelectedText();

    if (up) {
        move.movePosition(QTextCursor::PreviousBlock);
        move.insertBlock();
        move.movePosition(QTextCursor::Left);
    } else {
        move.movePosition(QTextCursor::EndOfBlock);
        if (move.atBlockStart()) { // empty block
            move.movePosition(QTextCursor::NextBlock);
            move.insertBlock();
            move.movePosition(QTextCursor::Left);
        } else {
            move.insertBlock();
        }
    }

    int start = move.position();
    move.clearSelection();
    move.insertText(text);
    int end = move.position();

    if (hasSelection) {
        move.setPosition(start);
        move.setPosition(end, QTextCursor::KeepAnchor);
    }

    reindent(document(), move);
    move.endEditBlock();

    setTextCursor(move);
    d->moveLineUndoHack = true;
}

void CodeEditor::cleanWhitespace()
{
    d->document->cleanWhitespace(textCursor());
}

void CodeEditor::setTextCursor(const QTextCursor &cursor)
{
    // workaround for QTextControl bug
    bool selectionChange = cursor.hasSelection() || textCursor().hasSelection();
    QTextCursor c = cursor;
    c.setVisualNavigation(true);
    QPlainTextEdit::setTextCursor(c);
    if (selectionChange)
        slotSelectionChanged();
}

/**
 * Moves text cursor to the specified line (optionally to `column` in that line).
 * @param column  [default=0]
 */
void CodeEditor::gotoLine(int line, int column)
{
    const int blockNumber = line - 1;
    const QTextBlock& block = document()->findBlockByNumber(blockNumber);
    if (block.isValid()) {
        QTextCursor cursor(block);
        if (column > 0) {
            cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, column);
        } else {
            int pos = cursor.position();
            const QTextDocument* doc = document();
            while (doc->characterAt(pos).category() == QChar::Separator_Space) {
                ++pos;
            }
            cursor.setPosition(pos);
        }
        setTextCursor(cursor);
        centerCursor();
    }
}

/**
 * Performs a specified `posOp` at `at` and returns the new position.
 */
int CodeEditor::position(ICodeEditor::PositionOperation posOp, int at) const
{
    QTextCursor tc = textCursor();

    if (at != -1)
        tc.setPosition(at);

    if (posOp == ICodeEditor::Current)
        return tc.position();

    switch (posOp) {
    case ICodeEditor::EndOfLine:
        tc.movePosition(QTextCursor::EndOfLine);
        return tc.position();
    case ICodeEditor::StartOfLine:
        tc.movePosition(QTextCursor::StartOfLine);
        return tc.position();
    case ICodeEditor::Anchor:
        if (tc.hasSelection())
            return tc.anchor();
        break;
    case ICodeEditor::EndOfDoc:
        tc.movePosition(QTextCursor::End);
        return tc.position();
    default:
        break;
    }

    return -1;
}

void CodeEditor::convertPosition(int pos, int *line, int *column) const
{
    QTextBlock block = document()->findBlock(pos);
    if (!block.isValid()) {
        (*line) = -1;
        (*column) = -1;
    } else {
        (*line) = block.blockNumber() + 1;
        (*column) = pos - block.position();
    }
}

/**
 * Returns plain text format of the contents.
 */
QString CodeEditor::contents() const
{
    return toPlainText();
}

/**
 * Returns the selected text in the document.
 */
QString CodeEditor::selectedText() const
{
    if (textCursor().hasSelection())
        return textCursor().selectedText();
    return QString();
}

/**
 * Returns the selected text specified by `pos` and `length`.
 */
QString CodeEditor::textAt(int pos, int length) const
{
    QTextCursor tc = textCursor();

    if (pos < 0)
        pos = 0;
    tc.movePosition(QTextCursor::End);
    if (pos + length > tc.position())
        length = tc.position() - pos;

    tc.setPosition(pos);
    tc.setPosition(pos + length, QTextCursor::KeepAnchor);

    return tc.selectedText();
}

/**
 * Returns the character at `pos`.
 */
QChar CodeEditor::characterAt(int pos) const
{
    return document()->characterAt(pos);
}

/**
 * Removes text in range of current position + `length`.
 */
void CodeEditor::remove(int length)
{
    QTextCursor tc = textCursor();
    tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
    tc.removeSelectedText();
}

/**
 * Inserts `string` into the editor at current position.
 */
void CodeEditor::insert(const QString &string)
{
    QTextCursor tc = textCursor();
    tc.insertText(string);
}

/**
 * Replaces the selection specified by current position and `length` with `string`.
 */
void CodeEditor::replace(int length, const QString &string)
{
    QTextCursor tc = textCursor();
    tc.setPosition(tc.position() + length, QTextCursor::KeepAnchor);
    tc.insertText(string);
}

/**
 * Puts text cursor at `pos`.
 */
void CodeEditor::setCursorPos(int pos)
{
    QTextCursor tc = textCursor();
    tc.setPosition(pos);
    setTextCursor(tc);
}

/**
 * Selects text from current position to `toPos`.
 */
void CodeEditor::select(int toPos)
{
    QTextCursor tc = textCursor();
    tc.setPosition(toPos, QTextCursor::KeepAnchor);
    setTextCursor(tc);
}

QString CodeEditor::displayName() const
{
    return d->displayName;
}

void CodeEditor::setDisplayName(const QString &title)
{
    d->displayName = title;
}

FileDocument *CodeEditor::fileDocument() const
{
    return d->document;
}

void CodeEditor::setFileDocument(FileDocument *document)
{
    if (document) {
        d->setupDocumentSignals(document);
        d->document = document;
    }
}

/**
 * @private, slot
 * Called before reload.
 */
void CodeEditor::memorizeCursorPosition()
{
    d->tempState = saveState();
}

/**
 * @private, slot
 * Called after reload.
 */
void CodeEditor::restoreCursorPosition()
{
    restoreState(d->tempState);
}

/**
 * @private, slot
 */
void CodeEditor::highlightSearchResults(const QString &txt, Find::IFindSupport::FindFlags findFlags)
{
    if (d->searchExpr.pattern() == txt)
        return;

    d->searchExpr.setPattern(txt);
    d->searchExpr.setPatternSyntax((findFlags & Find::IFindSupport::FindRegularExpression) ?
                                     QRegExp::RegExp : QRegExp::FixedString);
    d->searchExpr.setCaseSensitivity((findFlags & Find::IFindSupport::FindCaseSensitively) ?
                                       Qt::CaseSensitive : Qt::CaseInsensitive);
    d->findFlags = findFlags;
    viewport()->update();
}

/**
 * @reimp
 */
QByteArray CodeEditor::saveState() const
{
    QByteArray state;
    QDataStream stream(&state, QIODevice::WriteOnly);

    stream << 0; // version number
    stream << verticalScrollBar()->value();
    stream << horizontalScrollBar()->value();
    int line, column;
    convertPosition(textCursor().position(), &line, &column);
    stream << line;
    stream << column;

    // store code folding state
    QList<int> foldedBlocks;
    QTextBlock block = document()->firstBlock();
    while (block.isValid()) {
        if (block.userData() && static_cast<TextBlockUserData*>(block.userData())->folded()) {
            int number = block.blockNumber();
            foldedBlocks += number;
        }
        block = block.next();
    }
    stream << foldedBlocks;

    return state;
}

/**
 * @reimp
 */
bool CodeEditor::restoreState(const QByteArray &state)
{
    if (d->displaySettings.autoFoldFirstComment)
        d->foldLicenseHeader();

    if (state.isEmpty()) {
        return false;
    }

    int version;
    int vval, hval;
    int line, column;

    QDataStream stream(state);
    stream >> version;
    stream >> vval;
    stream >> hval;
    stream >> line;
    stream >> column;

    QList<int> foldedBlocks;
    stream >> foldedBlocks;
    QTextDocument *doc = document();
    foreach (int blockNumber, foldedBlocks) {
        QTextBlock block = doc->findBlockByNumber(qMax(0, blockNumber));
        if (block.isValid())
            CodeDocumentLayout::doFoldOrUnfold(block, false);
    }

    d->lastCursorChangeWasInteresting = false; // avoid adding last position to history
    gotoLine(line, column);
    verticalScrollBar()->setValue(vval);
    horizontalScrollBar()->setValue(hval);
    saveCurrentCursorPositionForNavigation();
    return true;
}

/**
 * Sets document file's default path.
 */
void CodeEditor::setDefaultPath(const QString &defaultPath)
{
    fileDocument()->setDefaultPath(defaultPath);
}

void CodeEditor::setSuggestedFileName(const QString &suggestedFileName)
{
    fileDocument()->setSuggestedFileName(suggestedFileName);
}

bool CodeEditor::isParenthesesMatchingEnabled() const
{
    return d->parenthesesMatchingEnabled;
}

void CodeEditor::setParenthesesMatchingEnabled(bool b)
{
    d->parenthesesMatchingEnabled = b;
}

bool CodeEditor::isAutoParenthesesEnabled() const
{
    return d->autoParenthesesEnabled;
}

void CodeEditor::setAutoParenthesesEnabled(bool b)
{
    d->autoParenthesesEnabled = b;
}

bool CodeEditor::highlightCurrentLine() const
{
    return d->highlightCurrentLine;
}

void CodeEditor::setHighlightCurrentLine(bool b)
{
    d->highlightCurrentLine = b;
    updateCurrentLineHighlight();
}

bool CodeEditor::lineNumbersVisible() const
{
    return d->lineNumbersVisible;
}

void CodeEditor::setLineNumbersVisible(bool b)
{
    d->lineNumbersVisible = b;
    slotUpdateExtraAreaWidth();
}

bool CodeEditor::marksVisible() const
{
    return d->marksVisible;
}

void CodeEditor::setMarksVisible(bool b)
{
    d->marksVisible = b;
    slotUpdateExtraAreaWidth();
}

bool CodeEditor::requestMarkEnabled() const
{
    return d->requestMarkEnabled;
}

void CodeEditor::setRequestMarkEnabled(bool b)
{
    d->requestMarkEnabled = b;
}

bool CodeEditor::lineSeparatorAllowed() const
{
    return d->lineSeparatorsAllowed;
}

void CodeEditor::setLineSeparatorAllowed(bool b)
{
    d->lineSeparatorsAllowed = b;
}

bool CodeEditor::codeFoldingVisible() const
{
    return d->codeFoldingVisible;
}

void CodeEditor::setCodeFoldingVisible(bool b)
{
    d->codeFoldingVisible = b && d->codeFoldingSupported;
    slotUpdateExtraAreaWidth();
}

bool CodeEditor::codeFoldingSupported() const
{
    return d->codeFoldingSupported;
}

/**
 * Sets whether code folding is supported by the syntax highlighter.
 * When not supported (the default), this makes sure the code folding is not shown.
 *
 * Needs to be called before calling setCodeFoldingVisible().
 */
void CodeEditor::setCodeFoldingSupported(bool b)
{
    d->codeFoldingSupported = b;
}

bool CodeEditor::revisionsVisible() const
{
    return d->revisionsVisible;
}

void CodeEditor::setRevisionsVisible(bool b)
{
    d->revisionsVisible = b;
    slotUpdateExtraAreaWidth();
}

int CodeEditor::visibleWrapColumn() const
{
    return d->visibleWrapColumn;
}

void CodeEditor::setVisibleWrapColumn(int column)
{
    d->visibleWrapColumn = column;
    viewport()->update();
}

QWidget *CodeEditor::extraArea() const
{
    return d->extraArea;
}

int CodeEditor::extraAreaWidth(int *markWidthPtr) const
{
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(document()->documentLayout());
    if (!docLayout)
        return 0;

    if (!d->marksVisible && docLayout->_hasMarks)
        d->marksVisible = true;

    int space = 0;
    const QFontMetrics fm(d->extraArea->fontMetrics());

    if (d->lineNumbersVisible) {
        QFont fnt = d->extraArea->font();
        // this works under the assumption that bold or italic can only make a font wider
        fnt.setBold(d->currentLineNumberFormat.font().bold());
        fnt.setItalic(d->currentLineNumberFormat.font().italic());
        const QFontMetrics linefm(fnt);

        int digits = 2;
        int max = qMax(1, blockCount());
        while (max >= 100) {
            max /= 10;
            ++digits;
        }
        space += linefm.width(QLatin1Char('9')) * digits;
    }
    int markWidth = 0;

    if (d->marksVisible) {
        markWidth += fm.lineSpacing();
        space += markWidth;
    } else {
        space += 2;
    }

    if (markWidthPtr)
        *markWidthPtr = markWidth;

    space += 4;

    if (d->codeFoldingVisible)
        space += foldBoxWidth(fm);
    return space;
}

/**
 * Zooms in by +10%.
 */
void CodeEditor::zoomIn()
{
    int fontZoom = d->fontZoom + 10;
    if (fontZoom > CodeEditorPrivate::fontZoomMax)
        fontZoom = CodeEditorPrivate::fontZoomMax;
    setFontZoom(fontZoom);
}

/**
 * Zooms out by -10%;
 */
void CodeEditor::zoomOut()
{
    int fontZoom = d->fontZoom - 10;
    if (fontZoom < CodeEditorPrivate::fontZoomMin)
        fontZoom = CodeEditorPrivate::fontZoomMin;
    setFontZoom(fontZoom);
}

/**
 * Zooms 100%.
 */
void CodeEditor::zoomReset()
{
    setFontZoom(100);
}

/**
 * @property fontZoom
 * Returns current font zoom ratio.
 * @sa setFontZoom(), fontZoomMin(), fontZoomMax()
 */
int CodeEditor::fontZoom() const
{
    return d->fontZoom;
}

/**
 * Zooms to specified ratio.
 */
void CodeEditor::setFontZoom(int fontZoom)
{
    if (fontZoom != d->fontZoom) {
        d->fontZoom = fontZoom;
        QFont f = d->fontSettings.font();
        f.setPointSize(d->fontSettings.fontSize() * d->fontZoom / 100);
        setFont(f);
        emit fontZoomChanged(d->fontZoom);
    }
}

/**
 * todo: This is language depended. Current implementation uses C++ convension.
 * Consider for extensibility.
 */
bool CodeEditor::isElectricCharacter(const QChar &ch) const
{
    if (ch == QLatin1Char('{') ||
        ch == QLatin1Char('}') ||
        ch == QLatin1Char(':') ||
        ch == QLatin1Char('#')) {
        return true;
    }
    return false;
}

/**
 * todo: impl
 * This is language depended. Consider for extensibility.
 */
QString CodeEditor::autoComplete(QTextCursor &cursor, const QString &text) const
{
    return QString();
}

/**
 * todo: impl
 * This is language depended. Consider for extensibility.
 */
bool CodeEditor::autoBackspace(QTextCursor &cursor)
{
    return false;
}

/**
 * todo: impl
 * This is language depended. Consider for extensibility.
 */
int CodeEditor::paragraphSeparatorAboutToBeInserted(QTextCursor &cursor)
{
    return 0;
}

/**
 * todo: impl
 * This is language depended. Consider for extensibility.
 */
void CodeEditor::indentBlock(QTextDocument *doc, QTextBlock block, QChar typedChar)
{
    // Empty implementation.
}

void CodeEditor::indent(QTextDocument *doc, const QTextCursor &cursor, QChar typedChar)
{
    if (cursor.hasSelection()) {
        QTextBlock block = doc->findBlock(cursor.selectionStart());
        const QTextBlock end = doc->findBlock(cursor.selectionEnd()).next();
        do {
            indentBlock(doc, block, typedChar);
            block = block.next();
        } while (block.isValid() && block != end);
    } else {
        indentBlock(doc, cursor.block(), typedChar);
    }
}

void CodeEditor::reindent(QTextDocument *doc, const QTextCursor &cursor)
{
    if (cursor.hasSelection()) {
        QTextBlock block = doc->findBlock(cursor.selectionStart());
        const QTextBlock end = doc->findBlock(cursor.selectionEnd()).next();

        const TabSettings &ts = tabSettings();

        // skip empty blocks
        while (block.isValid() && block != end) {
            QString bt = block.text();
            if (ts.firstNonSpace(bt) < bt.size())
                break;
            indentBlock(doc, block, QChar::Null);
            block = block.next();
        }

        int previousIndentation = ts.indentationColumn(block.text());
        indentBlock(doc, block, QChar::Null);
        int currentIndentation = ts.indentationColumn(block.text());
        int delta = currentIndentation - previousIndentation;

        block = block.next();
        while (block.isValid() && block != end) {
            ts.reindentLine(block, delta);
            block = block.next();
        }
    } else {
        indentBlock(doc, cursor.block(), QChar::Null);
    }
}

/**
 * Marks blocks as changed.
 * @sa revisionsVisible()
 */
void CodeEditor::markBlocksAsChanged(QList<int> blockNumbers)
{
    QTextBlock block = document()->begin();
    while (block.isValid()) {
        if (block.revision() < 0)
            block.setRevision(-block.revision() - 1);
        block = block.next();
    }
    foreach (const int blockNumber, blockNumbers) {
        QTextBlock block = document()->findBlockByNumber(blockNumber);
        if (block.isValid())
            block.setRevision(-block.revision() - 1);
    }
}

int CodeEditor::verticalBlockSelection() const
{
    if (!d->inBlockSelectionMode)
        return 0;

    QTextCursor begin = textCursor();
    QTextCursor end = begin;
    begin.setPosition(begin.selectionStart());
    end.setPosition(end.selectionEnd());

    int beginPositionInBlock = begin.position() - begin.block().position();
    int endPositionInBlock = end.position() - end.block().position();
    return qAbs(beginPositionInBlock - endPositionInBlock) + d->blockSelectionExtraX;
}

void CodeEditor::setFindScope(const QTextCursor &start, const QTextCursor &end, int verticalBlockSelection)
{
    if (start != d->findScopeStart || end != d->findScopeEnd) {
        d->findScopeStart = start;
        d->findScopeEnd = end;
        d->findScopeVerticalBlockSelection = verticalBlockSelection;
        viewport()->update();
    }
}

/**
 * @private, slot
 * On this->markContextMenuRequested(int, QMenu*) signal.
 */
void CodeEditor::requestMarkContextMenu(int line, QMenu* contextMenu)
{
    d->lineNumber = line;

    QAction *actToggleBookmark = new QAction(tr("Toggle Bookmark"), this);
    actToggleBookmark->setIcon(QIcon(":/bookmark"));
    connect(actToggleBookmark, SIGNAL(triggered()), this, SLOT(requestMark()));

    contextMenu->addAction(actToggleBookmark);
}

void CodeEditor::requestMark()
{
    requestMark(d->lineNumber);
}

/**
 * @private, slot
 * On this->markRequested(int) signal.
 */
void CodeEditor::requestMark(int line)
{
    DocumentMarker* docMarker = fileDocument()->documentMarker();
    Bookmarks::Bookmark* bookmark = _bookmarkManager->findBookmark(file()->fileName(), line);
    if (!bookmark) {
        bookmark = new Bookmarks::Bookmark(file()->fileName(), line, _bookmarkManager);
        docMarker->addMark(bookmark, line);
        _bookmarkManager->addBookmark(bookmark, true);
    } else {
        docMarker->removeMark(bookmark);
        _bookmarkManager->removeBookmark(bookmark);
    }
}

void CodeEditor::animateUpdate(int position, QPointF lastPos, QRectF rect)
{
    QTextCursor cursor(textCursor());
    cursor.setPosition(position);
    viewport()->update(QRectF(cursorRect(cursor).topLeft() + rect.topLeft(), rect.size()).toAlignedRect());
    if (!lastPos.isNull())
        viewport()->update(QRectF(lastPos + rect.topLeft(), rect.size()).toAlignedRect());
}

void CodeEditor::matchParentheses()
{
    if (isReadOnly())
        return;

    QTextCursor backwardMatch = textCursor();
    QTextCursor forwardMatch = textCursor();
    const TextBlockUserData::MatchType backwardMatchType = TextBlockUserData::matchCursorBackward(&backwardMatch);
    const TextBlockUserData::MatchType forwardMatchType = TextBlockUserData::matchCursorForward(&forwardMatch);

    QList<QTextEdit::ExtraSelection> extraSelections;

    if (backwardMatchType == TextBlockUserData::NoMatch && forwardMatchType == TextBlockUserData::NoMatch) {
        setExtraSelections(ParenthesesMatchingSelection, extraSelections); // clear
        return;
    }

    int animatePosition = -1;
    if (backwardMatch.hasSelection()) {
        QTextEdit::ExtraSelection sel;
        if (backwardMatchType == TextBlockUserData::Mismatch) {
            sel.cursor = backwardMatch;
            sel.format = d->mismatchFormat;
        } else {
            if (d->displaySettings.animateMatchingParentheses) {
                animatePosition = backwardMatch.selectionStart();
            } else if (d->formatRange) {
                sel.cursor = backwardMatch;
                sel.format = d->rangeFormat;
                extraSelections.append(sel);
            }

            sel.cursor = backwardMatch;
            sel.format = d->matchFormat;

            sel.cursor.setPosition(backwardMatch.selectionStart());
            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
            extraSelections.append(sel);

            sel.cursor.setPosition(backwardMatch.selectionEnd());
            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
        }
        extraSelections.append(sel);
    }

    if (forwardMatch.hasSelection()) {
        QTextEdit::ExtraSelection sel;
        if (forwardMatchType == TextBlockUserData::Mismatch) {
            sel.cursor = forwardMatch;
            sel.format = d->mismatchFormat;
        } else {
            if (d->displaySettings.animateMatchingParentheses) {
                animatePosition = forwardMatch.selectionEnd() - 1;
            } else if (d->formatRange) {
                sel.cursor = forwardMatch;
                sel.format = d->rangeFormat;
                extraSelections.append(sel);
            }

            sel.cursor = forwardMatch;
            sel.format = d->matchFormat;

            sel.cursor.setPosition(forwardMatch.selectionStart());
            sel.cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
            extraSelections.append(sel);

            sel.cursor.setPosition(forwardMatch.selectionEnd());
            sel.cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
        }
        extraSelections.append(sel);
    }

    if (animatePosition >= 0) {
        foreach (const QTextEdit::ExtraSelection &sel,
                 CodeEditor::extraSelections(ParenthesesMatchingSelection)) {
            if (sel.cursor.selectionStart() == animatePosition
             || sel.cursor.selectionEnd() - 1 == animatePosition) {
                animatePosition = -1;
                break;
            }
        }
    }

    if (animatePosition >= 0) {
        if (d->animator)
            d->animator->finish(); // one animation is enough
        d->animator = new CodeEditorAnimator(this);
        d->animator->setPosition(animatePosition);
        QPalette pal;
        pal.setBrush(QPalette::Text, d->matchFormat.foreground());
        pal.setBrush(QPalette::Base, d->rangeFormat.background());
        d->animator->setData(font(), pal, characterAt(d->animator->position()));
        connect(d->animator, SIGNAL(updateRequest(int, QPointF, QRectF)),
                this, SLOT(animateUpdate(int, QPointF, QRectF)));
    }

    setExtraSelections(ParenthesesMatchingSelection, extraSelections);
}

void CodeEditor::highlightBlocks()
{
    CodeEditorPrivateHighlightBlocks highlightBlocksInfo;

    QTextBlock block;
    if (d->extraAreaHighlightFoldedBlockNumber >= 0) {
        block = document()->findBlockByNumber(d->extraAreaHighlightFoldedBlockNumber);
        if (block.isValid()
         && block.next().isValid()
         && CodeDocumentLayout::foldingIndent(block.next())
          > CodeDocumentLayout::foldingIndent(block))
            block = block.next();
    }

    QTextBlock closeBlock = block;
    while (block.isValid()) {
        int foldingIndent = CodeDocumentLayout::foldingIndent(block);

        while (block.previous().isValid() && CodeDocumentLayout::foldingIndent(block) >= foldingIndent)
            block = block.previous();
        int nextIndent = CodeDocumentLayout::foldingIndent(block);
        if (nextIndent == foldingIndent)
            break;
        highlightBlocksInfo.open.prepend(block.blockNumber());
        while (closeBlock.next().isValid()
          && CodeDocumentLayout::foldingIndent(closeBlock.next()) >= foldingIndent)
            closeBlock = closeBlock.next();
        highlightBlocksInfo.close.append(closeBlock.blockNumber());
        int visualIndent = qMin(d->visualIndent(block), d->visualIndent(closeBlock));
        highlightBlocksInfo.visualIndent.prepend(visualIndent);
    }

    if (d->highlightBlocksInfo != highlightBlocksInfo) {
        d->highlightBlocksInfo = highlightBlocksInfo;
        viewport()->update();
        d->extraArea->update();
    }
}

QObject *CodeEditor::actionHack() const
{
    return d->actionHack;
}

void CodeEditor::setActionHack(QObject *hack)
{
    d->actionHack = hack;
}

/**
 * @reimp
 * Responds to ApplicationFontChange, FontChange event to change extraArea's font.
 * 'cos extraArea's line number font is the same as application's font.
 */
void CodeEditor::changeEvent(QEvent *event)
{
    QPlainTextEdit::changeEvent(event);
    if (event->type() == QEvent::ApplicationFontChange
        || event->type() == QEvent::FontChange) {
        if (d->extraArea) {
            QFont f = d->extraArea->font();
            f.setPointSize(font().pointSize());
            d->extraArea->setFont(f);
            slotUpdateExtraAreaWidth();
            d->extraArea->update();
        }
    }
}

/**
 * Selects the line under cursor before cut/copy without selection.
 */
void CodeEditor::maybeSelectLine()
{
    QTextCursor cursor = textCursor();
    if (!cursor.hasSelection()) {
        const QTextBlock &block = cursor.block();
        if (block.next().isValid()) {
            cursor.setPosition(block.position());
            cursor.setPosition(block.next().position(), QTextCursor::KeepAnchor);
        } else {
            cursor.movePosition(QTextCursor::EndOfBlock);
            cursor.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
            cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor);
        }
        setTextCursor(cursor);
    }
}

// Shift+Del
void CodeEditor::cutLine()
{
    maybeSelectLine();
    cut();
}

void CodeEditor::deleteLine()
{
    maybeSelectLine();
    textCursor().removeSelectedText();
}

QList<QTextEdit::ExtraSelection> CodeEditor::extraSelections(ExtraSelectionKind kind) const
{
    return d->extraSelections[kind];
}

void CodeEditor::setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections)
{
    if (selections.isEmpty() && d->extraSelections[kind].isEmpty())
        return;
    d->extraSelections[kind] = selections;

    QList<QTextEdit::ExtraSelection> all;
    for (int i = 0; i < NExtraSelectionKinds; ++i)
        all += d->extraSelections[i];

    QPlainTextEdit::setExtraSelections(all);
}

// the blocks list must be sorted
void CodeEditor::setIfdefedOutBlocks(const QList<CodeEditor::BlockRange> &blocks)
{
    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());
    if (!docLayout)
      return;

    bool needUpdate = false;

    QTextBlock block = doc->firstBlock();

    int rangeNumber = 0;
    int braceDepthDelta = 0;
    while (block.isValid()) {
        bool cleared = false;
        bool set = false;
        if (rangeNumber < blocks.size()) {
            const BlockRange &range = blocks.at(rangeNumber);
            if (block.position() >= range.first && ((block.position() + block.length() - 1) <= range.last || !range.last)) {
                set = CodeDocumentLayout::setIfdefedOut(block);
            } else {
                cleared = CodeDocumentLayout::clearIfdefedOut(block);
            }
            if (block.contains(range.last))
                ++rangeNumber;
        } else {
            cleared = CodeDocumentLayout::clearIfdefedOut(block);
        }

        if (cleared || set) {
            needUpdate = true;
            int delta = CodeDocumentLayout::braceDepthDelta(block);
            if (cleared)
                braceDepthDelta += delta;
            else if (set)
                braceDepthDelta -= delta;
        }

        if (braceDepthDelta)
            CodeDocumentLayout::changeBraceDepth(block,braceDepthDelta);

        block = block.next();
    }

    if (needUpdate)
        docLayout->requestUpdate();
}

void CodeEditor::format()
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();
    indent(document(), cursor, QChar::Null);
    cursor.endEditBlock();
}

void CodeEditor::rewrapParagraph()
{
    const int paragraphWidth = displaySettings().wrapColumn;
    const QRegExp anyLettersOrNumbers("\\w");
    const int tabSize = tabSettings().tabSize;

    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();

    // find start of paragraph
    while (cursor.movePosition(QTextCursor::PreviousBlock)) {
        QTextBlock block = cursor.block();
        QString text = block.text();

        // if this block is empty, move cursor back to previous and terminate.
        if (!text.contains(anyLettersOrNumbers)) {
            cursor.movePosition(QTextCursor::NextBlock);
            break;
        }
    }

    cursor.movePosition(QTextCursor::StartOfBlock);

    // find indent level of current block
    int indentLevel = 0;
    QString text = cursor.block().text();

    for (int i = 0; i < text.length(); i++) {
        const QChar ch = text.at(i);

        if (ch == QLatin1Char(' '))
            indentLevel++;
        else if (ch == QLatin1Char('\t'))
            indentLevel += tabSize - (indentLevel % tabSize);
        else
            break;
    }

    // if there is a common prefix, it should be kept and expanded to all lines.
    // this allows nice reflowing of doxygen style comments.
    QTextCursor nextBlock = cursor;
    QString commonPrefix;

    if (nextBlock.movePosition(QTextCursor::NextBlock)) {
        QString nText = nextBlock.block().text();
        int maxLength = qMin(text.length(), nText.length());

        for (int i = 0; i < maxLength; ++i) {
            const QChar ch = text.at(i);

            if (ch != nText[i] || ch.isLetterOrNumber())
                break;
            commonPrefix.append(ch);
        }
    }

    // find end of paragraph
    while (cursor.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor)) {
        QString text = cursor.block().text();
        if (!text.contains(anyLettersOrNumbers))
            break;
    }

    QString selectedText = cursor.selectedText();

    // preserve initial indent level or common prefix
    QString spacing;

    if (commonPrefix.isEmpty()) {
        spacing = tabSettings().indentationString(0, indentLevel);
    } else {
        spacing = commonPrefix;
        indentLevel = commonPrefix.length();
    }

    int currentLength = indentLevel;
    QString result;
    result.append(spacing);

    // Remove existing instances of any common prefix from paragraph to reflow.
    QString currentWord;

    for (int i = 0; i < selectedText.length(); ++i) {
        QChar ch = selectedText.at(i);
        if (ch.isSpace()) {
            if (!currentWord.isEmpty()) {
                currentLength += currentWord.length() + 1;

                if (currentLength > paragraphWidth) {
                    currentLength = currentWord.length() + 1 + indentLevel;
                    result.chop(1); // remove trailing space
                    result.append(QChar::ParagraphSeparator);
                    result.append(spacing);
                }

                result.append(currentWord);
                result.append(QLatin1Char(' '));
                currentWord.clear();
            }

            continue;
        }

        currentWord.append(ch);
    }
    result.chop(1);
    result.append(QChar::ParagraphSeparator);

    cursor.insertText(result);
    cursor.endEditBlock();
}

/**
 * todo: impl
 * This is language depended. Consider for extensibility.
 */
void CodeEditor::unCommentSelection()
{
    // Empty implementation.
}

void CodeEditor::loadSettings()
{
    QSettings settings;

    d->fontSettings.fromSettings(&settings);
    zoomReset();

//-
//    if (!d->fontSettings.isEmpty()) {
//        setFontSettings(d->fontSettings);
//        d->fontSettings.clear();
//    }
}

void CodeEditor::setFontSettings(const CodeEdit::FontSettings &fs)
{
    const QTextCharFormat textFormat = fs.toTextCharFormat(QLatin1String(Constants::C_TEXT));
    const QTextCharFormat selectionFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SELECTION));
    const QTextCharFormat lineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_LINE_NUMBER));
    const QTextCharFormat searchResultFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_RESULT));
    d->searchScopeFormat = fs.toTextCharFormat(QLatin1String(Constants::C_SEARCH_SCOPE));
    const QTextCharFormat parenthesesFormat = fs.toTextCharFormat(QLatin1String(Constants::C_PARENTHESES));
    d->currentLineFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE));
    d->currentLineNumberFormat = fs.toTextCharFormat(QLatin1String(Constants::C_CURRENT_LINE_NUMBER));
    d->ifdefedOutFormat = fs.toTextCharFormat(QLatin1String(Constants::C_DISABLED_CODE));
    QFont font(textFormat.font());

    const QColor foreground = textFormat.foreground().color();
    const QColor background = textFormat.background().color();
    QPalette pal = palette();
    pal.setColor(QPalette::Text, foreground);
    pal.setColor(QPalette::Foreground, foreground);
    pal.setColor(QPalette::Base, background);
    pal.setColor(QPalette::Highlight, (selectionFormat.background().style() != Qt::NoBrush) ?
               selectionFormat.background().color() :
               QApplication::palette().color(QPalette::Highlight));
    pal.setColor(QPalette::HighlightedText, selectionFormat.foreground().color());
    pal.setBrush(QPalette::Inactive, QPalette::Highlight, pal.highlight());
    pal.setBrush(QPalette::Inactive, QPalette::HighlightedText, pal.highlightedText());
    setPalette(pal);
    setFont(font);
    setTabSettings(d->document->tabSettings()); // update tabs, they depend on the font

    // Line numbers
    QPalette epal = d->extraArea->palette();
    epal.setColor(QPalette::Dark, lineNumberFormat.foreground().color());
    epal.setColor(QPalette::Background, lineNumberFormat.background().style() != Qt::NoBrush ?
                lineNumberFormat.background().color() : background);
    d->extraArea->setPalette(epal);

    // Search results
    d->searchResultFormat.setBackground(searchResultFormat.background());

    // Matching braces
    d->matchFormat.setForeground(parenthesesFormat.foreground());
    d->rangeFormat.setBackground(parenthesesFormat.background());

    slotUpdateExtraAreaWidth();   // Adjust to new font width
    updateCurrentLineHighlight(); // Make sure it takes the new color
}

void CodeEditor::setTabSettings(const CodeEdit::TabSettings &ts)
{
    d->document->setTabSettings(ts);
    int charWidth = QFontMetrics(font()).width(QChar(' '));
    setTabStopWidth(charWidth * ts.tabSize);
}

void CodeEditor::setDisplaySettings(const CodeEdit::DisplaySettings &ds)
{
    // color
    setlineNumbersColor(ds.lineNumbersColor);
    setLineNumbersFillColor(ds.lineNumbersFillColor);
    setFoldingMarkersFillColor(ds.foldingMarkersFillColor);
    setCurrentLineFillColor(ds.currentLineFillColor);
    setWrapEdgeColor(ds.wrapEdgeColor);
    setWrapEdgeFillColor(ds.wrapEdgeFillColor);

    setLineWrapMode(ds.textWrapping ? QPlainTextEdit::WidgetWidth : QPlainTextEdit::NoWrap);
    setLineNumbersVisible(ds.displayLineNumbers);
    setVisibleWrapColumn(ds.showWrapColumn ? ds.wrapColumn : 0);
    setHighlightCurrentLine(ds.highlightCurrentLine);
    setRevisionsVisible(ds.markTextChanges);

    if (d->displaySettings.visualizeWhitespace != ds.visualizeWhitespace) {
        if (QSyntaxHighlighter *highlighter = fileDocument()->syntaxHighlighter())
            highlighter->rehighlight();
        QTextOption option = document()->defaultTextOption();
        if (ds.visualizeWhitespace)
            option.setFlags(option.flags() | QTextOption::ShowTabsAndSpaces);
        else
            option.setFlags(option.flags() & ~QTextOption::ShowTabsAndSpaces);
        option.setFlags(option.flags() | QTextOption::AddSpaceForLineAndParagraphSeparators);
        document()->setDefaultTextOption(option);
    }

    d->displaySettings = ds;
    if (!ds.highlightBlocks) {
        d->extraAreaHighlightFoldedBlockNumber = -1;
        d->highlightBlocksInfo = CodeEditorPrivateHighlightBlocks();
    }

    slotCursorPositionChanged();
    viewport()->update();
    extraArea()->update();
}

void CodeEditor::setStorageSettings(const CodeEdit::StorageSettings &ss)
{
    d->document->setStorageSettings(ss);
}

void CodeEditor::fold()
{
    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());
    if (!docLayout)
        return;
    QTextBlock block = textCursor().block();
    while (!(CodeDocumentLayout::canFold(block) && block.next().isVisible())) {
        // find the closest previous block which can fold
        int indent = CodeDocumentLayout::foldingIndent(block);
        while (block.isValid() && (CodeDocumentLayout::foldingIndent(block) >= indent || !block.isVisible()))
            block = block.previous();
    }
    if (block.isValid()) {
        CodeDocumentLayout::doFoldOrUnfold(block, false);
        d->moveCursorVisible();
        docLayout->requestUpdate();
        docLayout->emitDocumentSizeChanged();
    }
}

void CodeEditor::unfold()
{
    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());
    if (!docLayout)
        return;
    QTextBlock block = textCursor().block();
    while (block.isValid() && !block.isVisible())
        block = block.previous();
    d->moveCursorVisible();
    docLayout->requestUpdate();
    docLayout->emitDocumentSizeChanged();
}

void CodeEditor::unfoldAll()
{
    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());
    if (!docLayout)
        return;
    QTextBlock block = doc->firstBlock();
    bool makeVisible = true;
    while (block.isValid()) {
        if (block.isVisible() && CodeDocumentLayout::canFold(block) && block.next().isVisible()) {
            makeVisible = false;
            break;
        }
        block = block.next();
    }

    block = doc->firstBlock();

    while (block.isValid()) {
        if (CodeDocumentLayout::canFold(block))
            CodeDocumentLayout::doFoldOrUnfold(block, makeVisible);
        block = block.next();
    }

    d->moveCursorVisible();
    docLayout->requestUpdate();
    docLayout->emitDocumentSizeChanged();
    centerCursor();
}

QTextCodec *CodeEditor::textCodec() const
{
    return fileDocument()->codec();
}

void CodeEditor::setTextCodec(QTextCodec *codec)
{
    fileDocument()->setCodec(codec);
}

void CodeEditor::setReadOnly(bool ro)
{
    QPlainTextEdit::setReadOnly(ro);
    if (ro)
        setTextInteractionFlags(textInteractionFlags() | Qt::TextSelectableByKeyboard);
}

//! Reimplemented to treat block selection mode.
void CodeEditor::cut()
{
    if (d->inBlockSelectionMode) {
        copy();
        d->removeBlockSelection();
        return;
    }
    QPlainTextEdit::cut();
}

//! Reimplemented to treat block selection mode.
void CodeEditor::paste()
{
    if (d->inBlockSelectionMode) {
        d->removeBlockSelection();
    }
    QPlainTextEdit::paste();
}

//! To support block selection.
QMimeData *CodeEditor::createMimeDataFromSelection() const
{
    if (d->inBlockSelectionMode) {
        QMimeData *mimeData = new QMimeData;
        QString text = d->copyBlockSelection();
        mimeData->setData(QLatin1String("application/vnd.codepad.vblocktext"), text.toUtf8());
        mimeData->setText(text); // for exchangeability
        return mimeData;
    } else if (textCursor().hasSelection()) {
        QTextCursor cursor = textCursor();
        QMimeData *mimeData = new QMimeData;

        // Copy the selected text as plain text
        QString text = cursor.selectedText();
        convertToPlainText(text);
        mimeData->setText(text);

        // Copy the selected text as HTML
        // create a new document from the selected text document fragment
        QTextDocument *tempDocument = new QTextDocument;
        QTextCursor tempCursor(tempDocument);
        tempCursor.insertFragment(cursor.selection());

        // apply the additional formats set by the syntax highlighter
        QTextBlock start = document()->findBlock(cursor.selectionStart());
        QTextBlock end = document()->findBlock(cursor.selectionEnd());
        end = end.next();

        const int selectionStart = cursor.selectionStart();
        const int endOfDocument = tempDocument->characterCount() - 1;
        for (QTextBlock current = start; current.isValid() && current != end; current = current.next()) {
            const QTextLayout *layout = current.layout();
            foreach (const QTextLayout::FormatRange &range, layout->additionalFormats()) {
                const int start = current.position() + range.start - selectionStart;
                const int end = start + range.length;
                if (end <= 0 || start >= endOfDocument)
                    continue;
                tempCursor.setPosition(qMax(start, 0));
                tempCursor.setPosition(qMin(end, endOfDocument), QTextCursor::KeepAnchor);
                tempCursor.setCharFormat(range.format);
            }
        }

        // reset the user states since they are not interesting
        for (QTextBlock block = tempDocument->begin(); block.isValid(); block = block.next())
            block.setUserState(-1);

        // make sure the text appears pre-formatted
        tempCursor.setPosition(0);
        tempCursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
        QTextBlockFormat blockFormat = tempCursor.blockFormat();
        blockFormat.setNonBreakableLines(true);
        tempCursor.setBlockFormat(blockFormat);

        mimeData->setHtml(tempCursor.selection().toHtml());
        delete tempDocument;

        /* Try to figure out whether we are copying an entire block, and store the
         * complete block including indentation in the codepad.blocktext mimetype.
         */
        QTextCursor selStart = cursor;
        selStart.setPosition(cursor.selectionStart());
        QTextCursor selEnd = cursor;
        selEnd.setPosition(cursor.selectionEnd());
        const TabSettings &ts = d->document->tabSettings();

        bool startOk = ts.cursorIsAtBeginingOfLine(selStart);
        bool multipleBlocks = (selEnd.block() != selEnd.block());

        if (startOk && multipleBlocks) {
            selStart.movePosition(QTextCursor::StartOfBlock);
            if (ts.cursorIsAtBeginingOfLine(selEnd))
                selEnd.movePosition(QTextCursor::StartOfBlock);
            cursor.setPosition(selStart.position());
            cursor.setPosition(selEnd.position(), QTextCursor::KeepAnchor);
            text = cursor.selectedText();
            mimeData->setData(QLatin1String("application/vnd.codepad.blocktext"), text.toUtf8());
        }

        return mimeData;
    }
    return 0;
}

bool CodeEditor::canInsertFromMimeData(const QMimeData *source) const
{
    return QPlainTextEdit::canInsertFromMimeData(source);
}

void CodeEditor::insertFromMimeData(const QMimeData *source)
{
    if (isReadOnly())
        return;

    if (source->hasFormat(QLatin1String("application/vnd.codepad.vblocktext"))) {
        QString text = QString::fromUtf8(source->data(QLatin1String("application/vnd.codepad.vblocktext")));
        if (text.isEmpty())
            return;
        QStringList lines = text.split(QLatin1Char('\n'));
        QTextCursor cursor = textCursor();
        cursor.beginEditBlock();
        int initialCursorPosition = cursor.position();
        int column = cursor.position() - cursor.block().position();
        cursor.insertText(lines.first());
        for (int i = 1; i < lines.count(); ++i) {
            QTextBlock next = cursor.block().next();
            if (next.isValid()) {
                cursor.setPosition(next.position() + qMin(column, next.length()-1));
            } else {
                cursor.movePosition(QTextCursor::EndOfBlock);
                cursor.insertBlock();
            }

            int actualColumn = cursor.position() - cursor.block().position();
            if (actualColumn < column)
                cursor.insertText(QString(column - actualColumn, QLatin1Char(' ')));
            cursor.insertText(lines.at(i));
        }
        cursor.setPosition(initialCursorPosition);
        cursor.endEditBlock();
        setTextCursor(cursor);
        ensureCursorVisible();
        return;
    }

    QString text = source->text();
    if (text.isEmpty())
        return;

    const TabSettings &ts = d->document->tabSettings();
    QTextCursor cursor = textCursor();
    if (!ts.autoIndent) {
        cursor.beginEditBlock();
        cursor.insertText(text);
        cursor.endEditBlock();
        setTextCursor(cursor);
        return;
    }

    cursor.beginEditBlock();
    cursor.removeSelectedText();

    bool insertAtBeginningOfLine = ts.cursorIsAtBeginingOfLine(cursor);

    if (insertAtBeginningOfLine
     && source->hasFormat(QLatin1String("application/vnd.codepad.blocktext"))) {
        text = QString::fromUtf8(source->data(QLatin1String("application/vnd.codepad.blocktext")));
        if (text.isEmpty())
            return;
    }

    int reindentBlockStart = cursor.blockNumber() + (insertAtBeginningOfLine ? 0 : 1);

    bool hasFinalNewLine = text.endsWith(QLatin1Char('\n'))
                        || text.endsWith(QChar::ParagraphSeparator)
                        || text.endsWith(QLatin1Char('\r'));

    if (insertAtBeginningOfLine
     && hasFinalNewLine) // since we'll add a final newline, preserve current line's indentation
        cursor.setPosition(cursor.block().position());

    int cursorPosition = cursor.position();
    cursor.insertText(text);

    int reindentBlockEnd = cursor.blockNumber() - (hasFinalNewLine ? 1 : 0);

    if (reindentBlockStart < reindentBlockEnd
        || (reindentBlockStart == reindentBlockEnd
            && (!insertAtBeginningOfLine || !hasFinalNewLine))) {
        if (insertAtBeginningOfLine && !hasFinalNewLine) {
            QTextCursor unnecessaryWhitespace = cursor;
            unnecessaryWhitespace.setPosition(cursorPosition);
            unnecessaryWhitespace.movePosition(QTextCursor::StartOfBlock, QTextCursor::KeepAnchor);
            unnecessaryWhitespace.removeSelectedText();
        }
        QTextCursor c = cursor;
        c.setPosition(cursor.document()->findBlockByNumber(reindentBlockStart).position());
        c.setPosition(cursor.document()->findBlockByNumber(reindentBlockEnd).position(),
                      QTextCursor::KeepAnchor);
        reindent(document(), c);
    }

    cursor.endEditBlock();
    setTextCursor(cursor);
}

/**
 * @private, slot
 * On this->blockCountChange() signal and several explicit calls.
 */
void CodeEditor::slotUpdateExtraAreaWidth()
{
    if (isLeftToRight())
        setViewportMargins(extraAreaWidth(), 0, 0, 0);
    else
        setViewportMargins(0, 0, extraAreaWidth(), 0);
}

/**
 * @private, slot
 * On this->updateRequest(QRect, int) signal.
 */
void CodeEditor::slotUpdateRequest(const QRect &r, int dy)
{
    if (dy) {
        d->extraArea->scroll(0, dy);
    }
    else if (r.width() > 4) { // wide than cursor width, not just cursor blinking
        d->extraArea->update(0, r.y(), d->extraArea->width(), r.height());
    }

    if (r.contains(viewport()->rect()))
        slotUpdateExtraAreaWidth();
}

/**
 * @override
 * Adjusts inner widgets' geometry on resize.
 */
void CodeEditor::resizeEvent(QResizeEvent* event)
{
    QPlainTextEdit::resizeEvent(event);
    QRect cr = contentsRect();
    d->extraArea->setGeometry(QStyle::visualRect(
        layoutDirection(), cr,
        QRect(cr.left(), cr.top(), extraAreaWidth(), cr.height())));
}

/**
 * @override
 * @note QWheelEvent::delta():
 * A positive value indicates that the wheel was rotated forwards away from the user.
 */
void CodeEditor::wheelEvent(QWheelEvent* event)
{
    d->clearVisibleFoldedBlock();
    if (event->modifiers() & Qt::ControlModifier) {
        const int delta = event->delta();
        if (delta > 0) {
            zoomIn();
        } else if (delta < 0) {
            zoomOut();
        }
    }
    QPlainTextEdit::wheelEvent(event);
}

/**
 * @reimp, protected
 * The Tab and Shift+Tab keys are special. QPlainTextEdit::event() handles them
 * before it calls keyPressEvent(), with the semantic of passing the focus to
 * the next or previous widget in the focus chain.
 * We prefer to make Tab indent a block or selection and Shift+Tab unindent.
 * So we should add our custom processing here.
 */
bool CodeEditor::event(QEvent* event)
{
    if (event->type() == QEvent::KeyPress) {
        QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
        if (keyEvent->key() == Qt::Key_Tab) {
            onTabKey(keyEvent);
            return true;
        }
        else if (keyEvent->key() == Qt::Key_Backtab
                 || (keyEvent->key() == Qt::Key_Tab && keyEvent->modifiers() & Qt::ShiftModifier)) {
            onShiftTabKey(keyEvent);
            return true;
        }
    }
    // Ctrl+MidButton resets zooming.
    if (event->type() == QEvent::MouseButtonPress) {
        QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
        if (mouseEvent->button() == Qt::MidButton
            && (mouseEvent->modifiers() & Qt::ControlModifier)) {
            zoomReset();
            return true;
        }
    }
    return QPlainTextEdit::event(event);
}

/**
 * @override
 * Catches some special keys to provide custom processing. (TAB, RETURN, etc.)
 */
void CodeEditor::keyPressEvent(QKeyEvent* event)
{
    switch (event->key()) {
    case Qt::Key_Home:
        onHomeKey(event);
        break;
    case Qt::Key_Return:
        onReturnKey(event);
        break;
    case Qt::Key_Backspace:
        onBackspaceKey(event);
        break;
    case Qt::Key_Slash:
        if (event->modifiers() & Qt::ControlModifier) {
            onCtrlSlashKey(event);
        } else {
            QPlainTextEdit::keyPressEvent(event);
        }
        break;
    default:
        QPlainTextEdit::keyPressEvent(event);
    }
}

/**
 * On 'Tab' key pressed.
 * (1) Translate 'tab' into 4 spaces if there's no selection.
 * (2) Otherwise, indent the selection.
 */
void CodeEditor::onTabKey(QKeyEvent* event)
{
    if (!textCursor().hasSelection()) {
        // insert 4 spaces instead of tab
        if (d->document->tabSettings().spacesForTabs) {
            textCursor().insertText(QString(d->document->tabSettings().indentSize, QLatin1Char(' ')));
        } else {
            QPlainTextEdit::keyPressEvent(event);
        }
    }
    else {
        indentOrUnindent(true);
    }
}

/**
 * On 'BackTab'(Shift+Tab) key pressed.
 * (1) Moves position backward 4 characters if there's no selection.
 * (2) Otherwise, unindent the selection.
 */
void CodeEditor::onShiftTabKey(QKeyEvent* event)
{
    if (!textCursor().hasSelection()) {
        // moves position backward 4 characters (indentSize)
        QTextCursor cursor = textCursor();
        int newPos = cursor.position() - d->document->tabSettings().indentSize;
        if (newPos < 0) {
            newPos = 0;
        }
        cursor.setPosition(newPos);
        setTextCursor(cursor);
    }
    else {
        indentOrUnindent(false);
    }
}

/**
 * On 'Home' key pressed.
 * Moves text cursor to the first word, or, beginning of the block.
 */
void CodeEditor::onHomeKey(QKeyEvent* event)
{
    QTextCursor cursor = textCursor();
    int oldPos = cursor.position();
    int blockStartPos = cursor.block().position();
    QRegExp spacePattern("^( +|\t+)");
    if (spacePattern.indexIn(cursor.block().text()) != -1) {
        int wordStartPos = blockStartPos + spacePattern.matchedLength();
        if (cursor.atBlockStart()) {
            // puts text cursor just in front of the first word, if previously at block start
            cursor.setPosition(wordStartPos);
        } else {
            if (oldPos > wordStartPos) {
                // was after the first word
                cursor.setPosition(wordStartPos);
            } else {
                // was before the first word
                cursor.setPosition(blockStartPos);
            }
        }
        setTextCursor(cursor);
    } else {
        QPlainTextEdit::keyPressEvent(event);
    }
}

/**
 * Indents selection.
 * default: tab key interpreted as 4 spaces
 * todo: configurable
 */
void CodeEditor::indentOrUnindent(bool doIndent)
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();

    // Indent or unindent the selected lines
    int pos = cursor.position();
    int anchor = cursor.anchor();
    int start = qMin(anchor, pos);
    int end = qMax(anchor, pos);

    QTextDocument *doc = document();
    QTextBlock startBlock = doc->findBlock(start);
    QTextBlock endBlock = doc->findBlock(end-1).next();

    for (QTextBlock block = startBlock; block != endBlock; block = block.next()) {
        QString text = block.text();
        if (doIndent) {
            int indentPosition = d->document->tabSettings().firstNonSpace(text);
            cursor.setPosition(block.position() + indentPosition);
            if (!d->document->tabSettings().spacesForTabs) {
                cursor.insertText(QString(1, QLatin1Char('\t')));
            } else {
                cursor.insertText(QString(tabSettings().indentSize, QLatin1Char(' ')));
            }
        } else {
            int indentPosition = tabSettings().firstNonSpace(text);
            int targetColumn = tabSettings().indentedColumn(tabSettings().columnAt(text, indentPosition), false);
            cursor.setPosition(block.position() + indentPosition);
            cursor.setPosition(block.position() + targetColumn, QTextCursor::KeepAnchor);
            cursor.removeSelectedText();
        }
    }

    // Reselect the selected lines
    cursor.setPosition(startBlock.position());
    cursor.setPosition(endBlock.previous().position(), QTextCursor::KeepAnchor);
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);

    cursor.endEditBlock();
    setTextCursor(cursor);
}

/**
 * On 'Return' key pressed.
 * (1) If there's no spaces in the beginning of current line, call inherited behavior.
 * (2) Otherwise, make proper indentation automatically.
 */
void CodeEditor::onReturnKey(QKeyEvent* event)
{
    QTextCursor cursor = textCursor();
    QString text = cursor.block().text();

    int indent = tabSettings().firstNonSpace(text);
//+    int indentationString = tabSettings().indentationString(text);
    if (indent) {
        cursor.beginEditBlock();

        if (!tabSettings().spacesForTabs)
            indent = qCeil(indent / tabSettings().indentSize);

        // add 1 extra indent if current line begins a code block
        bool inCodeBlock = false;
        if (tabSettings().autoIndent) {
            if (QRegExp("\\{").indexIn(text) != -1) {
                indent += tabSettings().spacesForTabs ? tabSettings().indentSize : 1;
                inCodeBlock = true;
            }
        }

        cursor.insertBlock();
        QString spaces(indent, tabSettings().spacesForTabs ? QLatin1Char(' ') : QLatin1Char('\t'));
        cursor.insertText(spaces);

        // add code block end "}" if needed
        if (tabSettings().autoIndent && inCodeBlock) {
            if (QRegExp("\\}").indexIn(cursor.block().next().text()) == -1) {
                cursor.insertBlock();
                if (tabSettings().spacesForTabs)
                    spaces = QString(indent - tabSettings().indentSize, QLatin1Char(' '));
                else
                    spaces = QString(indent - 1, QLatin1Char('\t'));
                spaces += QLatin1Char('}');
                cursor.insertText(spaces);
                cursor.movePosition(QTextCursor::PreviousBlock);
                cursor.movePosition(QTextCursor::EndOfBlock);
            }
        }

        cursor.endEditBlock();
        setTextCursor(cursor);
    }
    else {
        QPlainTextEdit::keyPressEvent(event);
    }
}

/**
 * On 'Backspace' key pressed.
 * (1) If previous char is not space, call inherited.
 * (2) Otherwise, unindent properly (4 spaces).
 */
void CodeEditor::onBackspaceKey(QKeyEvent* event)
{
    if (!tabSettings().smartBackspace) {
        QPlainTextEdit::keyPressEvent(event);
        return;
    }

    QTextCursor cursor = textCursor();
    int oldPos = cursor.position();
    int blockStartPos = cursor.block().position();
    QRegExp spacePattern("^( +|\t)");
    if (spacePattern.indexIn(cursor.block().text()) != -1) {
        int wordStartPos = blockStartPos + spacePattern.matchedLength();
        if (oldPos <= wordStartPos) {
            cursor.beginEditBlock();
            // determine selection to delete
            int newPos = oldPos - 4;
            if (newPos < blockStartPos) {
                newPos = blockStartPos;
            }
            // make selection
            cursor.setPosition(oldPos);
            cursor.setPosition(newPos, QTextCursor::KeepAnchor);
            cursor.deleteChar();
            cursor.endEditBlock();
            setTextCursor(cursor);
        } else {
            QPlainTextEdit::keyPressEvent(event);
        }
    }
    else {
        QPlainTextEdit::keyPressEvent(event);
    }
}

/**
 * 'Ctrl + /' toggles multiple singleline comment state.
 */
void CodeEditor::onCtrlSlashKey(QKeyEvent* event)
{
    QTextCursor cursor = textCursor();
    cursor.beginEditBlock();

    // previous selection state
    int selStart = cursor.selectionStart();
    int selEnd = cursor.selectionEnd();
    cursor.setPosition(selEnd);
    int blockEnd = cursor.blockNumber();

    // extend selStart to first blocks's start-of-block
    // extend selEnd to last block's end-of-block
    cursor.setPosition(selStart);
    cursor.movePosition(QTextCursor::StartOfBlock);
    selStart = cursor.position();
    cursor.setPosition(selEnd);
    cursor.movePosition(QTextCursor::EndOfBlock);
    selEnd = cursor.position();

    // process first block
    cursor.setPosition(selStart);
    QRegExp commentPattern("^//");
    if (commentPattern.indexIn(cursor.block().text()) == -1) {
        // comment it, if the block does not starts with '//'
        cursor.insertText(QString(2, '/'));
        selEnd += 2;
    }
    else {
        // else uncomment it
        cursor.setPosition(selStart + commentPattern.matchedLength(), QTextCursor::KeepAnchor);
        cursor.deleteChar();
        selEnd -= 2;
    }

    // loop through all blocks
    while (cursor.blockNumber() < blockEnd) {
        cursor.movePosition(QTextCursor::NextBlock);
        cursor.movePosition(QTextCursor::StartOfBlock);
        if (commentPattern.indexIn(cursor.block().text()) == -1) {
            cursor.insertText(QString(2, '/'));
            selEnd += 2;
        }
        else {
            cursor.setPosition(cursor.position() + commentPattern.matchedLength(), QTextCursor::KeepAnchor);
            cursor.deleteChar();
            selEnd -= 2;
        }
    }

    // restore selection state
    cursor.setPosition(selStart);
    cursor.setPosition(selEnd, QTextCursor::KeepAnchor);

    // update
    cursor.endEditBlock();
    setTextCursor(cursor);
}

namespace CodeEdit {
    namespace Internal {
        struct BlockSelectionData {
            int selectionIndex;
            int selectionStart;
            int selectionEnd;
            int firstColumn;
            int lastColumn;
        };
    }
}

static QColor blendColors(const QColor &a, const QColor &b, int alpha)
{
    return QColor((a.red()   * (256 - alpha) + b.red()   * alpha) / 256,
                  (a.green() * (256 - alpha) + b.green() * alpha) / 256,
                  (a.blue()  * (256 - alpha) + b.blue()  * alpha) / 256);
}

static QColor calcBlendColor(const QColor &baseColor, int level, int count)
{
    QColor color80;
    QColor color90;

    if (baseColor.value() > 128) {
        const int f90 = 15;
        const int f80 = 30;
        color80.setRgb(qMax(0, baseColor.red() - f80),
                       qMax(0, baseColor.green() - f80),
                       qMax(0, baseColor.blue() - f80));
        color90.setRgb(qMax(0, baseColor.red() - f90),
                       qMax(0, baseColor.green() - f90),
                       qMax(0, baseColor.blue() - f90));
    } else {
        const int f90 = 20;
        const int f80 = 40;
        color80.setRgb(qMin(255, baseColor.red() + f80),
                       qMin(255, baseColor.green() + f80),
                       qMin(255, baseColor.blue() + f80));
        color90.setRgb(qMin(255, baseColor.red() + f90),
                       qMin(255, baseColor.green() + f90),
                       qMin(255, baseColor.blue() + f90));
    }

    if (level == count)
        return baseColor;
    if (level == 0)
        return color80;
    if (level == count - 1)
        return color90;

    const int blendFactor = level * (256 / (count - 2));

    return blendColors(color80, color90, blendFactor);
}

void CodeEditor::paintEvent(QPaintEvent *event)
{
    /* Here comes an almost verbatim copy of
     * QPlainTextEdit::paintEvent() so we can adjust the extra
     * selections dynamically to indicate all search results.
     */

    // begin QPlainTextEdit::paintEvent() copy

    QPainter painter(viewport());
    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());
    if (!docLayout)
        return;

    QPointF offset(contentOffset());
    QTextBlock textCursorBlock = textCursor().block();

    bool hasMainSelection = textCursor().hasSelection();
    bool suppressSyntaxInIfdefedOutBlock = (d->ifdefedOutFormat.foreground()
                                           != palette().foreground());

    QRect er = event->rect();
    QRect viewportRect = viewport()->rect();

    const QColor baseColor = palette().base().color();

    qreal lineX = 0;

    if (d->visibleWrapColumn > 0) {
        lineX = fontMetrics().averageCharWidth() * d->visibleWrapColumn + offset.x() + 4;

        if (lineX < viewportRect.width()) {
            const QBrush background = d->ifdefedOutFormat.background();
            painter.fillRect(QRectF(lineX, er.top(), viewportRect.width() - lineX, er.height()),
                             background);

            const QColor col = (palette().base().color().value() > 128) ? Qt::black : Qt::white;
            const QPen pen = painter.pen();
            painter.setPen(blendColors(background.color(), col, 32));
            painter.drawLine(QPointF(lineX, er.top()), QPointF(lineX, er.bottom()));
            painter.setPen(pen);
        }
    }

    // Set a brush origin so that the WaveUnderline knows where the wave started
    painter.setBrushOrigin(offset);

    bool editable = !isReadOnly();
    QTextBlock block = firstVisibleBlock();

    QAbstractTextDocumentLayout::PaintContext context = getPaintContext();

    if (!d->highlightBlocksInfo.isEmpty()) {
        // extra pass for the block highlight

        const int margin = 5;
        QTextBlock blockFP = block;
        QPointF offsetFP = offset;
        while (blockFP.isValid()) {
            QRectF r = blockBoundingRect(blockFP).translated(offsetFP);

            int n = blockFP.blockNumber();
            int depth = 0;
            foreach (int i, d->highlightBlocksInfo.open)
                if (n >= i)
                    ++depth;
            foreach (int i, d->highlightBlocksInfo.close)
                if (n > i)
                    --depth;

            int count = d->highlightBlocksInfo.count();
            if (count) {
                QRectF rr = r;
                rr.setWidth(viewport()->width());
                if (lineX > 0)
                    rr.setRight(qMin(lineX, rr.right()));
                for (int i = 0; i <= depth; ++i) {
                    int vi = i > 0 ? d->highlightBlocksInfo.visualIndent.at(i-1) : 0;
                    painter.fillRect(rr.adjusted(vi, 0, -8*i, 0), calcBlendColor(baseColor, i, count));
                }

            }
            offsetFP.ry() += r.height();

            if (offsetFP.y() > viewportRect.height() + margin)
                break;

            blockFP = blockFP.next();
            if (!blockFP.isVisible()) {
                // invisible blocks do have zero line count
                blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
            }
        }
    }

    if (!d->findScopeStart.isNull()) {

        CodeEditorOverlay *overlay = new CodeEditorOverlay(this);
        overlay->addOverlaySelection(d->findScopeStart.position(),
                                     d->findScopeEnd.position(),
                                     d->searchScopeFormat.background().color().darker(120),
                                     d->searchScopeFormat.background().color(),
                                     CodeEditorOverlay::ExpandBegin,
                                     d->findScopeVerticalBlockSelection);
        overlay->setAlpha(false);
        overlay->paint(&painter, event->rect());
        delete overlay;
    }

    BlockSelectionData *blockSelection = 0;

    if (d->inBlockSelectionMode
        && context.selections.count() && context.selections.last().cursor == textCursor()) {
        blockSelection = new BlockSelectionData;
        blockSelection->selectionIndex = context.selections.size()-1;
        const QAbstractTextDocumentLayout::Selection &selection = context.selections[blockSelection->selectionIndex];
        int start = blockSelection->selectionStart = selection.cursor.selectionStart();
        int end = blockSelection->selectionEnd = selection.cursor.selectionEnd();
        QTextBlock block = doc->findBlock(start);
        int columnA = start - block.position();
        block = doc->findBlock(end);
        int columnB = end - block.position();
        blockSelection->firstColumn = qMin(columnA, columnB);
        blockSelection->lastColumn = qMax(columnA, columnB) + d->blockSelectionExtraX;
    }

    QTextBlock visibleCollapsedBlock;
    QPointF visibleCollapsedBlockOffset;

    QTextLayout *cursor_layout = 0;
    QPointF cursor_offset;
    int cursor_cpos = 0;
    QPen cursor_pen;

//-
//    d->_searchResultOverlay->clear();
//    if (!d->searchExpr.isEmpty()) { // first pass for the search result overlays
//
//        const int margin = 5;
//        QTextBlock blockFP = block;
//        QPointF offsetFP = offset;
//        while (blockFP.isValid()) {
//            QRectF r = blockBoundingRect(blockFP).translated(offsetFP);
//
//            if (r.bottom() >= er.top() - margin && r.top() <= er.bottom() + margin) {
//                d->highlightSearchResults(blockFP, d->_searchResultOverlay);
//            }
//            offsetFP.ry() += r.height();
//
//            if (offsetFP.y() > viewportRect.height() + margin)
//                break;
//
//            blockFP = blockFP.next();
//            if (!blockFP.isVisible()) {
//                // invisible blocks do have zero line count
//                blockFP = doc->findBlockByLineNumber(blockFP.firstLineNumber());
//            }
//        }
//
//    } // end first pass
//
//    d->_searchResultOverlay->fill(&painter,
//                                   d->_searchResultFormat.background().color(),
//                                   event->rect());

    while (block.isValid()) {

        QRectF r = blockBoundingRect(block).translated(offset);

        if (r.bottom() >= er.top() && r.top() <= er.bottom()) {

            if (CodeDocumentLayout::ifdefedOut(block)) {
                QRectF rr = r;
                rr.setRight(viewportRect.width() - offset.x());
                if (lineX > 0)
                    rr.setRight(qMin(lineX, rr.right()));
                painter.fillRect(rr, d->ifdefedOutFormat.background());
            }

            QTextLayout *layout = block.layout();

            QTextOption option = layout->textOption();
            if (suppressSyntaxInIfdefedOutBlock && CodeDocumentLayout::ifdefedOut(block)) {
                option.setFlags(option.flags() | QTextOption::SuppressColors);
                painter.setPen(d->ifdefedOutFormat.foreground().color());
            } else {
                option.setFlags(option.flags() & ~QTextOption::SuppressColors);
                painter.setPen(context.palette.text().color());
            }
            layout->setTextOption(option);
            layout->setFont(doc->defaultFont()); // this really should be in QPlaintextEdit when creating the layout!

            int blpos = block.position();
            int bllen = block.length();

            QVector<QTextLayout::FormatRange> selections;
            QVector<QTextLayout::FormatRange> prioritySelections;

            for (int i = 0; i < context.selections.size(); ++i) {
                const QAbstractTextDocumentLayout::Selection &range = context.selections.at(i);
                const int selStart = range.cursor.selectionStart() - blpos;
                const int selEnd = range.cursor.selectionEnd() - blpos;
                if (selStart < bllen && selEnd >= 0
                    && selEnd >= selStart) {
                    QTextLayout::FormatRange o;
                    o.start = selStart;
                    o.length = selEnd - selStart;
                    o.format = range.format;
                    if (blockSelection && blockSelection->selectionIndex == i) {
                        o.start = qMin(blockSelection->firstColumn, bllen-1);
                        o.length = qMin(blockSelection->lastColumn, bllen-1) - o.start;
                    }
                    if ((hasMainSelection && i == context.selections.size()-1)
                        || (o.format.foreground().style() == Qt::NoBrush
                        && o.format.underlineStyle() != QTextCharFormat::NoUnderline
                        && o.format.background() == Qt::NoBrush))
                        prioritySelections.append(o);
                    else
                        selections.append(o);
                }
            }
            selections += prioritySelections;

            if (d->highlightCurrentLine && block == textCursorBlock) {
                /* QTextCursor::positionInBlock() is equivalent to {position() - block.position()}
                 * This function is added in 4.7.
                 * @see http://qt.gitorious.org/qt/qt/blobs/4.7/src/gui/text/qtextcursor.cpp
                 */
                int positionInBlock = textCursor().position() - textCursor().block().position();
                QRectF rr = layout->lineForTextPosition(positionInBlock).rect();
                rr.moveTop(rr.top() + r.top());
                rr.setLeft(0);
                rr.setRight(viewportRect.width() - offset.x());
                QColor color = d->currentLineFormat.background().color();
                // set alpha, otherwise we cannot see block highlighting and find scope underneath
                color.setAlpha(128);
                painter.fillRect(rr, color);
            }

            bool drawCursor = ((editable || true) // we want the cursor in read-only mode
                               && context.cursorPosition >= blpos
                               && context.cursorPosition < blpos + bllen);

            bool drawCursorAsBlock = drawCursor && overwriteMode() ;

            if (drawCursorAsBlock) {
                int relativePos = context.cursorPosition - blpos;
                bool doSelection = true;
                QTextLine line = layout->lineForTextPosition(relativePos);
                qreal x = line.cursorToX(relativePos);
                qreal w = 0;
                if (relativePos < line.textLength() - line.textStart()) {
                    w = line.cursorToX(relativePos + 1) - x;
                    if (doc->characterAt(context.cursorPosition) == QLatin1Char('\t')) {
                        doSelection = false;
                        int space = QFontMetrics(layout->font()).width(QLatin1Char(' '));
                        if (w > space) {
                            x += w-space;
                            w = space;
                        }
                    }
                } else
                    w = QFontMetrics(layout->font()).width(QLatin1Char(' ')); // in sync with QTextLine::draw()

                QRectF rr = line.rect();
                rr.moveTop(rr.top() + r.top());
                rr.moveLeft(r.left() + x);
                rr.setWidth(w);
                painter.fillRect(rr, palette().text());
                if (doSelection) {
                    QTextLayout::FormatRange o;
                    o.start = relativePos;
                    o.length = 1;
                    o.format.setForeground(palette().base());
                    selections.append(o);
                }
            }

            layout->draw(&painter, offset, selections, er);

            if ((drawCursor && !drawCursorAsBlock)
                || (editable && context.cursorPosition < -1
                    && !layout->preeditAreaText().isEmpty())) {
                int cpos = context.cursorPosition;
                if (cpos < -1)
                    cpos = layout->preeditAreaPosition() - (cpos + 2);
                else
                    cpos -= blpos;
                cursor_layout = layout;
                cursor_offset = offset;
                cursor_cpos = cpos;
                cursor_pen = painter.pen();
            }
        }

        offset.ry() += r.height();

        if (offset.y() > viewportRect.height())
            break;

        block = block.next();

        if (!block.isVisible()) {
            if (block.blockNumber() == d->visibleFoldedBlockNumber) {
                visibleCollapsedBlock = block;
                visibleCollapsedBlockOffset = offset + QPointF(0,1);
            }

            // invisible blocks do have zero line count
            block = doc->findBlockByLineNumber(block.firstLineNumber());
        }
    }
    painter.setPen(context.palette.text().color());

    if (backgroundVisible() && !block.isValid() && offset.y() <= er.bottom()
        && (centerOnScroll() || verticalScrollBar()->maximum() == verticalScrollBar()->minimum())) {
        painter.fillRect(QRect(QPoint((int)er.left(), (int)offset.y()), er.bottomRight()), palette().background());
    }

    // end QPlainTextEdit::paintEvent() copy

    delete blockSelection;

    offset = contentOffset();
    block = firstVisibleBlock();

    int top = (int)blockBoundingGeometry(block).translated(offset).top();
    int bottom = top + (int)blockBoundingRect(block).height();

    QTextCursor cursor = textCursor();
    bool hasSelection = cursor.hasSelection();
    int selectionStart = cursor.selectionStart();
    int selectionEnd = cursor.selectionEnd();

    while (block.isValid() && top <= event->rect().bottom()) {
        QTextBlock nextBlock = block.next();
        QTextBlock nextVisibleBlock = nextBlock;

        if (!nextVisibleBlock.isVisible()) {
            // invisible blocks do have zero line count
            nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
            // paranoia in case our code somewhere did not set the line count
            // of the invisible block to 0
            while (nextVisibleBlock.isValid() && !nextVisibleBlock.isVisible())
                nextVisibleBlock = nextVisibleBlock.next();
        }
        if (block.isVisible() && bottom >= event->rect().top()) {
            if (d->displaySettings.visualizeWhitespace) {
                QTextLayout *layout = block.layout();
                int lineCount = layout->lineCount();
                if (lineCount >= 2 || !nextBlock.isValid()) {
                    painter.save();
                    painter.setPen(Qt::lightGray);
                    for (int i = 0; i < lineCount-1; ++i) { // paint line wrap indicator
                        QTextLine line = layout->lineAt(i);
                        QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                        QChar visualArrow((ushort)0x21b5);
                        painter.drawText(static_cast<int>(lineRect.right()),
                                         static_cast<int>(lineRect.top() + line.ascent()), visualArrow);
                    }
                    if (!nextBlock.isValid()) { // paint EOF symbol
                        QTextLine line = layout->lineAt(lineCount-1);
                        QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                        int h = 4;
                        lineRect.adjust(0, 0, -1, -1);
                        QPainterPath path;
                        QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent()));
                        path.moveTo(pos);
                        path.lineTo(pos + QPointF(-h, -h));
                        path.lineTo(pos + QPointF(0, -2*h));
                        path.lineTo(pos + QPointF(h, -h));
                        path.closeSubpath();
                        painter.setBrush(painter.pen().color());
                        painter.drawPath(path);
                    }
                    painter.restore();
                }
            }

            if (nextBlock.isValid() && !nextBlock.isVisible()) {

                bool selectThis = (hasSelection
                                   && nextBlock.position() >= selectionStart
                                   && nextBlock.position() < selectionEnd);
                if (selectThis) {
                    painter.save();
                    painter.setBrush(palette().highlight());
                }

                QTextLayout *layout = block.layout();
                QTextLine line = layout->lineAt(layout->lineCount()-1);
                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                lineRect.adjust(0, 0, -1, -1);

                QRectF collapseRect(lineRect.right() + 12,
                                    lineRect.top(),
                                    fontMetrics().width(QLatin1String(" {...}; ")),
                                    lineRect.height());
                painter.setRenderHint(QPainter::Antialiasing, true);
                painter.translate(.5, .5);
                painter.drawRoundedRect(collapseRect.adjusted(0, 0, 0, -1), 3, 3);
                painter.setRenderHint(QPainter::Antialiasing, false);
                painter.translate(-.5, -.5);

                QString replacement = QLatin1String("...");

                if (TextBlockUserData *nextBlockUserData = CodeDocumentLayout::testUserData(nextBlock)) {
                    if (nextBlockUserData->foldingStartIncluded())
                        replacement.prepend(nextBlock.text().trimmed().left(1));
                }

                block = nextVisibleBlock.previous();
                if (!block.isValid())
                    block = doc->lastBlock();

                if (TextBlockUserData *blockUserData = CodeDocumentLayout::testUserData(block)) {
                    if (blockUserData->foldingEndIncluded()) {
                        QString right = block.text().trimmed();
                        if (right.endsWith(QLatin1Char(';'))) {
                            right.chop(1);
                            right = right.trimmed();
                            replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
                            replacement.append(QLatin1Char(';'));
                        } else {
                            replacement.append(right.right(right.endsWith(QLatin1Char('/')) ? 2 : 1));
                        }
                    }
                }

                if (selectThis)
                    painter.setPen(palette().highlightedText().color());
                painter.drawText(collapseRect, Qt::AlignCenter, replacement);
                if (selectThis)
                    painter.restore();
            }
        }

        block = nextVisibleBlock;
        top = bottom;
        bottom = top + (int)blockBoundingRect(block).height();
    }

    if (visibleCollapsedBlock.isValid() ) {
        int margin = doc->documentMargin();
        qreal maxWidth = 0;
        qreal blockHeight = 0;
        QTextBlock b = visibleCollapsedBlock;

        while (!b.isVisible()) {
            b.setVisible(true); // make sure block bounding rect works
            QRectF r = blockBoundingRect(b).translated(visibleCollapsedBlockOffset);

            QTextLayout *layout = b.layout();
            for (int i = layout->lineCount()-1; i >= 0; --i)
                maxWidth = qMax(maxWidth, layout->lineAt(i).naturalTextWidth() + 2*margin);

            blockHeight += r.height();

            b.setVisible(false); // restore previous state
            b.setLineCount(0); // restore 0 line count for invisible block
            b = b.next();
        }

        painter.save();
        painter.setRenderHint(QPainter::Antialiasing, true);
        painter.translate(.5, .5);
        QBrush brush = baseColor;
        if (d->ifdefedOutFormat.hasProperty(QTextFormat::BackgroundBrush))
            brush = d->ifdefedOutFormat.background();
        painter.setBrush(brush);
        painter.drawRoundedRect(QRectF(visibleCollapsedBlockOffset.x(),
                                       visibleCollapsedBlockOffset.y(),
                                       maxWidth, blockHeight).adjusted(0, 0, 0, 0), 3, 3);
        painter.restore();

        QTextBlock end = b;
        b = visibleCollapsedBlock;
        while (b != end) {
            b.setVisible(true); // make sure block bounding rect works
            QRectF r = blockBoundingRect(b).translated(visibleCollapsedBlockOffset);
            QTextLayout *layout = b.layout();
            QVector<QTextLayout::FormatRange> selections;
            layout->draw(&painter, visibleCollapsedBlockOffset, selections, er);

            b.setVisible(false); // restore previous state
            visibleCollapsedBlockOffset.ry() += r.height();
            b = b.next();
        }
    }

    if (d->animator && d->animator->isRunning()) {
        QTextCursor cursor = textCursor();
        cursor.setPosition(d->animator->position());
        d->animator->draw(&painter, cursorRect(cursor).topLeft());
    }

    // draw the cursor last, on top of everything
    if (cursor_layout) {
        painter.setPen(cursor_pen);
        cursor_layout->drawCursor(&painter, cursor_offset, cursor_cpos, cursorWidth());
    }
}

/**
 * @protected, override
 * Overrides dragEnterEvent(), dragMoveEvent(), dropEvent() to support
 * file dropping, in addition to QPlainTextEdit's built-in text dropping.
 */
void CodeEditor::dragEnterEvent(QDragEnterEvent* event)
{
    // hasUrls() = "text/url-list" = CF_HDROP
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
    } else {
        QPlainTextEdit::dragEnterEvent(event);
    }
}

/**
 * @protected, override
 * @note Should be overridden, otherwise dropEvent() won't be called.
 */
void CodeEditor::dragMoveEvent(QDragMoveEvent* event)
{
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
    } else {
        QPlainTextEdit::dragMoveEvent(event);
    }
}

/**
 * @protected, override
 * Does nothing about the file dropped.
 * Just emits a fileDropped() signal to let the application process it.
 */
void CodeEditor::dropEvent(QDropEvent* event)
{
    if (event->mimeData()->hasUrls()) {
        event->acceptProposedAction();
        emit fileDropped(event->mimeData()->urls().at(0).toLocalFile());
    } else {
        QPlainTextEdit::dropEvent(event);
    }
}

/**
 * @private, slot
 * On this->cursorPositionChanged() signal.
 * Uses extra selection to decorate current line.
 * You can set the background color here.
 */
void CodeEditor::slotCursorPositionChanged()
{
    if (!d->contentsChanged && d->lastCursorChangeWasInteresting) {
//+        addCurrentPositionToNavigationHistory(editableInterface(), d->_tempNavigationState);
        d->lastCursorChangeWasInteresting = false;
    }
    else if (d->contentsChanged) {
        saveCurrentCursorPositionForNavigation();
    }
    updateHighlights();
//-
//    QList<QTextEdit::ExtraSelection> extraSelections;
//
//    if (!isReadOnly()) {
//        QTextEdit::ExtraSelection selection;
//
//        QColor lineColor = d->displaySettings.currentLineFillColor;
//        lineColor.setAlpha(128);
//
//        selection.format.setBackground(lineColor);
//        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
//        selection.cursor = textCursor();
//        selection.cursor.clearSelection();
//        extraSelections.append(selection);
//    }
//
//    setExtraSelections(extraSelections);
}

void CodeEditor::updateHighlights()
{
    if (d->parenthesesMatchingEnabled && hasFocus()) {
        // Delay update when no matching is displayed yet, to avoid flicker
        if (extraSelections(ParenthesesMatchingSelection).isEmpty()
         && d->animator == 0) {
            d->parenthesesMatchingTimer->start(50);
        } else {
            // use 0-timer, not direct call, to give syntax highlighter a chance
            // to update the parentheses information
            d->parenthesesMatchingTimer->start(0);
        }
    }

    updateCurrentLineHighlight();

    if (d->displaySettings.highlightBlocks) {
        QTextCursor cursor = textCursor();
        d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
        d->highlightBlocksTimer->start(100);
    }
}

void CodeEditor::updateCurrentLineHighlight()
{
    QList<QTextEdit::ExtraSelection> extraSelections;

    if (d->highlightCurrentLine) {
        QTextEdit::ExtraSelection sel;
        sel.format.setBackground(d->currentLineFormat.background());
        sel.format.setProperty(QTextFormat::FullWidthSelection, true);
        sel.cursor = textCursor();
        sel.cursor.clearSelection();
        extraSelections.append(sel);
    }

    setExtraSelections(CurrentLineSelection, extraSelections);

    // The extra area shows information for the entire current block, not just
    // the current line.
    // This is why we must force a bigger update region.
    int cursorBlockNumber = textCursor().blockNumber();
    if (cursorBlockNumber != d->cursorBlockNumber) {
        QPointF offset = contentOffset();
        QTextBlock block = document()->findBlockByNumber(d->cursorBlockNumber);
        if (block.isValid())
            d->extraArea->update(blockBoundingGeometry(block).translated(offset).toAlignedRect());
        block = document()->findBlockByNumber(cursorBlockNumber);
        if (block.isValid())
            d->extraArea->update(blockBoundingGeometry(block).translated(offset).toAlignedRect());
        d->cursorBlockNumber = cursorBlockNumber;
    }
}

void CodeEditor::slotUpdateBlockNotify(const QTextBlock &block)
{
    static bool blockRecursion = false;
    if (blockRecursion)
        return;
    if (block.previous().isValid() && block.userState() != block.previous().userState()) {
        /* The syntax highlighting state changes. This opens up for
         * the possibility that the paragraph has braces that support
         * code folding. In this case, do the save thing and also
         * update the previous block, which might contain a collapse
         * box which now is invalid.*/
        blockRecursion = true;
        emit requestBlockUpdate(block.previous());
        blockRecursion = false;
    }
}

/**
 * @reimp
 * The main event handler for the scrolling area (the viewport() widget).
 * It handles the event specified, and can be called by subclasses to provide
 * reasonable default behavior.
 * Returns true to indicate to the event system that the event has been handled,
 * and needs no further processing; otherwise returns false to indicate that the
 * event should be propagated further.
 */
bool CodeEditor::viewportEvent(QEvent *event)
{
    d->contentsChanged = false;
    if (event->type() == QEvent::ContextMenu) {
        const QContextMenuEvent *ce = static_cast<QContextMenuEvent*>(event);
        if (ce->reason() == QContextMenuEvent::Mouse && !textCursor().hasSelection())
            setTextCursor(cursorForPosition(ce->pos()));
    }
    else if (event->type() == QEvent::ToolTip) {
        const QHelpEvent *he = static_cast<QHelpEvent*>(event);

        if (QApplication::keyboardModifiers() & Qt::ControlModifier)
            return true; // swallow tooltip event when Ctrl is pressed

        const QPoint &pos = he->pos();

        // Request tooltip showing
        const QTextCursor &c = cursorForPosition(pos);
        QPoint cursorPos = mapToGlobal(cursorRect(c).bottomRight() + QPoint(1, 1));
        cursorPos.setX(cursorPos.x() + d->extraArea->width());

        emit tooltipRequested(cursorPos, c.position());

        return true;
    }
    return QPlainTextEdit::viewportEvent(event);
}

/**
 * This event handler can be reimplemented in a subclass to receive timer events
 * for the object.
 * QTimer provides a higher-level interface to the timer functionality, and also
 * more general information about timers. The timer event is passed in the event parameter.
 */
void CodeEditor::timerEvent(QTimerEvent *event)
{
    if (event->timerId() == d->autoScrollTimer.timerId()) {
        const QPoint globalPos = QCursor::pos();
        const QPoint pos = d->extraArea->mapFromGlobal(globalPos);
        QRect visible = d->extraArea->rect();
        verticalScrollBar()->triggerAction(pos.y() < visible.center().y() ?
                                           QAbstractSlider::SliderSingleStepSub :
                                           QAbstractSlider::SliderSingleStepAdd);
        QMouseEvent ev(QEvent::MouseMove, pos, globalPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
        extraAreaMouseEvent(&ev);
        int delta = qMax(pos.y() - visible.top(), visible.bottom() - pos.y()) - visible.height();
        if (delta < 7)
            delta = 7;
        int timeout = 4900 / (delta * delta);
        d->autoScrollTimer.start(timeout, this);
    }
    else if (event->timerId() == d->foldedBlockTimer.timerId()) {
        d->visibleFoldedBlockNumber = d->suggestedVisibleFoldedBlockNumber;
        d->suggestedVisibleFoldedBlockNumber = -1;
        d->foldedBlockTimer.stop();
        viewport()->update();
    }
    QPlainTextEdit::timerEvent(event);
}

void CodeEditor::mouseMoveEvent(QMouseEvent *event)
{
    d->lastEventWasBlockSelectionEvent = (event->modifiers() & Qt::AltModifier);

    if (event->buttons() == Qt::NoButton) {
        const QTextBlock foldedBlock = foldedBlockAt(event->pos());
        const int blockNumber = foldedBlock.next().blockNumber();
        if (blockNumber < 0) {
            d->clearVisibleFoldedBlock();
        }
        else if (blockNumber != d->visibleFoldedBlockNumber) {
            d->suggestedVisibleFoldedBlockNumber = blockNumber;
            d->foldedBlockTimer.start(40, this);
        }

        // Update the mouse cursor
        if (foldedBlock.isValid() && !d->mouseOnFoldedMarker) {
            d->mouseOnFoldedMarker = true;
        }
        else if (!foldedBlock.isValid() && d->mouseOnFoldedMarker) {
            d->mouseOnFoldedMarker = false;
            viewport()->setCursor(Qt::IBeamCursor);
        }
    } else {
        QPlainTextEdit::mouseMoveEvent(event);
    }

    if (d->lastEventWasBlockSelectionEvent && d->inBlockSelectionMode) {
        if (textCursor().atBlockEnd()) {
            d->blockSelectionExtraX = qMax(0, event->pos().x() - cursorRect().center().x()) / fontMetrics().averageCharWidth();
        } else {
            d->blockSelectionExtraX = 0;
        }
    }

    if (viewport()->cursor().shape() == Qt::BlankCursor)
        viewport()->setCursor(Qt::IBeamCursor);
}

void CodeEditor::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        d->clearBlockSelection(); // just in case, otherwise we might get strange drag and drop

        QTextBlock foldedBlock = foldedBlockAt(event->pos());
        if (foldedBlock.isValid()) {
            toggleBlockVisible(foldedBlock);
            viewport()->setCursor(Qt::IBeamCursor);
        }
    }

    QPlainTextEdit::mousePressEvent(event);
}

void CodeEditor::extraAreaLeaveEvent(QEvent *event)
{
    Q_UNUSED(event);
    // fake missing mouse move event from Qt
    QMouseEvent me(QEvent::MouseMove, QPoint(-1, -1), Qt::NoButton, 0, 0);
    extraAreaMouseEvent(&me);
}

/**
 * @reimp, protected
 * Handles extraArea.mousePressEvent(), ..mouseMoveEvent(), ..mouseReleaseEvent()
 */
void CodeEditor::extraAreaMouseEvent(QMouseEvent *event)
{
    QTextCursor cursor = cursorForPosition(QPoint(0, event->pos().y()));

    int markWidth;
    extraAreaWidth(&markWidth);

    if (d->codeFoldingVisible
        && event->type() == QEvent::MouseMove && event->buttons() == 0) { // mouse tracking
        // Update which folding marker is highlighted
        const int highlightBlockNumber = d->extraAreaHighlightFoldedBlockNumber;
        d->extraAreaHighlightFoldedBlockNumber = -1;

        if (event->pos().x() > d->extraArea->width() - foldBoxWidth(fontMetrics())) {
            d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
        } else if (d->displaySettings.highlightBlocks) {
            QTextCursor cursor = textCursor();
            d->extraAreaHighlightFoldedBlockNumber = cursor.blockNumber();
        }

        if (highlightBlockNumber != d->extraAreaHighlightFoldedBlockNumber)
            d->highlightBlocksTimer->start(d->highlightBlocksInfo.isEmpty() ? 120 : 0);
    }

    // Set whether the mouse cursor is a hand or a normal arrow
    if (event->type() == QEvent::MouseMove) {
        bool hand = (event->pos().x() <= markWidth);
        if (hand != (d->extraArea->cursor().shape() == Qt::PointingHandCursor))
            d->extraArea->setCursor(hand ? Qt::PointingHandCursor : Qt::ArrowCursor);
    }

    if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick) {
        if (event->button() == Qt::LeftButton) {
            int boxWidth = foldBoxWidth(fontMetrics());
            if (d->codeFoldingVisible && event->pos().x() > d->extraArea->width() - boxWidth) {
                //! code folding area clicked
                if (!cursor.block().next().isVisible()) {
                    toggleBlockVisible(cursor.block());
                    d->moveCursorVisible(false);
                }
                else if (foldBox().contains(event->pos())) {
                    cursor.setPosition(document()->findBlockByNumber(d->highlightBlocksInfo.open.last()).position());
                    QTextBlock c = cursor.block();
                    toggleBlockVisible(c);
                    d->moveCursorVisible(false);
                }
            }
            else if (d->marksVisible && event->pos().x() > markWidth) {
                //! line numbers area clicked: select current line
                QTextCursor selection = cursor;
                selection.setVisualNavigation(true);
                d->extraAreaSelectionAnchorBlockNumber = selection.blockNumber();
                selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
                selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
                setTextCursor(selection);
            }
            else {
                //! marks area clicked
                d->extraAreaToggleMarkBlockNumber = cursor.blockNumber();
            }
        }
        else if (d->marksVisible && event->button() == Qt::RightButton) {
            //! right clicked: show context menu
            QMenu *contextMenu = new QMenu(this);
            emit markContextMenuRequested(cursor.blockNumber() + 1, contextMenu);
            if (!contextMenu->isEmpty())
                contextMenu->exec(event->globalPos());
            delete contextMenu;
        }
    }
    else if (d->extraAreaSelectionAnchorBlockNumber >= 0) {
        QTextCursor selection = cursor;
        selection.setVisualNavigation(true);
        if (event->type() == QEvent::MouseMove) {
            QTextBlock anchorBlock = document()->findBlockByNumber(d->extraAreaSelectionAnchorBlockNumber);
            selection.setPosition(anchorBlock.position());
            if (cursor.blockNumber() < d->extraAreaSelectionAnchorBlockNumber) {
                selection.movePosition(QTextCursor::EndOfBlock);
                selection.movePosition(QTextCursor::Right);
            }
            selection.setPosition(cursor.block().position(), QTextCursor::KeepAnchor);
            if (cursor.blockNumber() >= d->extraAreaSelectionAnchorBlockNumber) {
                selection.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
                selection.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor);
            }

            if (event->pos().y() >= 0 && event->pos().y() <= d->extraArea->height())
                d->autoScrollTimer.stop();
            else if (!d->autoScrollTimer.isActive())
                d->autoScrollTimer.start(100, this);
        }
        else {
            d->autoScrollTimer.stop();
            d->extraAreaSelectionAnchorBlockNumber = -1;
            return;
        }
        setTextCursor(selection);
    }
    else if (d->extraAreaToggleMarkBlockNumber >= 0 && d->marksVisible && d->requestMarkEnabled) {
        if (event->type() == QEvent::MouseButtonRelease && event->button() == Qt::LeftButton) {
            int n = d->extraAreaToggleMarkBlockNumber;
            d->extraAreaToggleMarkBlockNumber = -1;
            if (cursor.blockNumber() == n) {
                int line = n + 1;
                emit markRequested(line);
            }
        }
    }
}

/**
 * @private, slot
 * Updates revision states on this->modificationChanged(bool) signal.
 */
void CodeEditor::slotModificationChanged(bool modified)
{
    if (modified) {
        return;
    }

    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());
    if (!docLayout) {
        return;
    }
    int oldLastSaveRevision = docLayout->_lastSaveRevision;
    docLayout->_lastSaveRevision = doc->revision();

    if (oldLastSaveRevision != docLayout->_lastSaveRevision) {
        QTextBlock block = doc->begin();
        while (block.isValid()) {
            if (block.revision() < 0 || block.revision() != oldLastSaveRevision) {
                block.setRevision(-docLayout->_lastSaveRevision - 1);
            } else {
                block.setRevision(docLayout->_lastSaveRevision);
            }
            block = block.next();
        }
    }
    d->extraArea->update();
}

void CodeEditor::ensureCursorVisible()
{
    QTextBlock block = textCursor().block();
    if (!block.isVisible()) {
        while (!block.isVisible() && block.previous().isValid())
            block = block.previous();
        toggleBlockVisible(block);
    }
    QPlainTextEdit::ensureCursorVisible();
}

void CodeEditor::toggleBlockVisible(const QTextBlock &block)
{
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(document()->documentLayout());
    if (docLayout) {
        bool visible = block.next().isVisible();
        CodeDocumentLayout::doFoldOrUnfold(block, !visible);
        docLayout->requestUpdate();
        docLayout->emitDocumentSizeChanged();
    }
}

const TabSettings &CodeEditor::tabSettings() const
{
    return d->document->tabSettings();
}

const DisplaySettings &CodeEditor::displaySettings() const
{
    return d->displaySettings;
}

const StorageSettings &CodeEditor::storageSettings() const
{
    return d->document->storageSettings();
}

QColor CodeEditor::wrapEdgeFillColor()
{
    return d->displaySettings.wrapEdgeFillColor;
}

void CodeEditor::setWrapEdgeFillColor(QColor value)
{
    if (d->displaySettings.wrapEdgeFillColor != value) {
        d->displaySettings.wrapEdgeFillColor = value;
        viewport()->update();
    }
}

QColor CodeEditor::wrapEdgeColor()
{
    return d->displaySettings.wrapEdgeColor;
}

void CodeEditor::setWrapEdgeColor(QColor value)
{
    if (d->displaySettings.wrapEdgeColor != value) {
        d->displaySettings.wrapEdgeColor = value;
        viewport()->update();
    }
}

int CodeEditor::wrapColumn()
{
    return d->displaySettings.wrapColumn;
}

void CodeEditor::setWrapColumn(int value)
{
    if (d->displaySettings.wrapColumn != value) {
        d->displaySettings.wrapColumn = value;
        viewport()->update();
    }
}

QColor CodeEditor::currentLineFillColor()
{
    return d->displaySettings.currentLineFillColor;
}

void CodeEditor::setCurrentLineFillColor(QColor value)
{
    if (d->displaySettings.currentLineFillColor != value) {
        d->displaySettings.currentLineFillColor = value;
        viewport()->update();
    }
}

QColor CodeEditor::lineNumbersFillColor()
{
    return d->displaySettings.lineNumbersFillColor;
}

void CodeEditor::setLineNumbersFillColor(QColor value)
{
    if (d->displaySettings.lineNumbersFillColor != value) {
        d->displaySettings.lineNumbersFillColor = value;
        viewport()->update();
    }
}

QColor CodeEditor::lineNumbersColor()
{
    return d->displaySettings.lineNumbersColor;
}

void CodeEditor::setlineNumbersColor(QColor value)
{
    if (d->displaySettings.lineNumbersColor != value) {
        d->displaySettings.lineNumbersColor = value;
        viewport()->update();
    }
}

QColor CodeEditor::foldingMarkersFillColor()
{
    return d->displaySettings.foldingMarkersFillColor;
}

void CodeEditor::setFoldingMarkersFillColor(QColor value)
{
    if (d->displaySettings.foldingMarkersFillColor != value) {
        d->displaySettings.foldingMarkersFillColor = value;
        viewport()->update();
    }
}

int CodeEditor::currentLine() const
{
    return textCursor().blockNumber() + 1;
}

int CodeEditor::currentColumn() const
{
    QTextCursor cursor = textCursor();
    return cursor.position() - cursor.block().position() + 1;
}

static QColor mergedColors(const QColor &colorA, const QColor &colorB, int factor)
{
    const int maxFactor = 100;
    QColor tmp = colorA;
    tmp.setRed((tmp.red() * factor) / maxFactor + (colorB.red() * (maxFactor - factor)) / maxFactor);
    tmp.setGreen((tmp.green() * factor) / maxFactor + (colorB.green() * (maxFactor - factor)) / maxFactor);
    tmp.setBlue((tmp.blue() * factor) / maxFactor + (colorB.blue() * (maxFactor - factor)) / maxFactor);
    return tmp;
}

static void drawRectBox(QPainter *painter, const QRect &rect, bool start, bool end,
                        const QPalette &pal)
{
    painter->save();
    painter->setRenderHint(QPainter::Antialiasing, false);

    QRgb b = pal.base().color().rgb();
    QRgb h = pal.highlight().color().rgb();
    QColor c = mergedColors(b, h, 50);

    QLinearGradient grad(rect.topLeft(), rect.topRight());
    grad.setColorAt(0, c.lighter(110));
    grad.setColorAt(1, c.lighter(130));
    QColor outline = c;

    painter->fillRect(rect, grad);
    painter->setPen(outline);
    if (start)
        painter->drawLine(rect.topLeft() + QPoint(1, 0), rect.topRight() -  QPoint(1, 0));
    if (end)
        painter->drawLine(rect.bottomLeft() + QPoint(1, 0), rect.bottomRight() -  QPoint(1, 0));

    painter->drawLine(rect.topRight() + QPoint(0, start ? 1 : 0), rect.bottomRight() - QPoint(0, end ? 1 : 0));
    painter->drawLine(rect.topLeft() + QPoint(0, start ? 1 : 0), rect.bottomLeft() - QPoint(0, end ? 1 : 0));

    painter->restore();
}

void CodeEditor::extraAreaPaintEvent(QPaintEvent* event)
{
    QTextDocument *doc = document();
    CodeDocumentLayout *docLayout = qobject_cast<CodeDocumentLayout*>(doc->documentLayout());

    int selStart = textCursor().selectionStart();
    int selEnd = textCursor().selectionEnd();

    const QColor baseColor = palette().base().color();
    QPalette pal = d->extraArea->palette();
    pal.setCurrentColorGroup(QPalette::Active);
    QPainter painter(d->extraArea);
    const QFontMetrics fm(d->extraArea->font());
    int fmLineSpacing = fm.lineSpacing();

    int markWidth = 0;
    if (d->marksVisible)
        markWidth += fm.lineSpacing();

    const int collapseColumnWidth = d->codeFoldingVisible ? foldBoxWidth(fm) : 0;
    const int extraAreaWidth = d->extraArea->width() - collapseColumnWidth;

    painter.fillRect(event->rect(), pal.color(QPalette::Base));
    painter.fillRect(event->rect().intersected(QRect(0, 0, extraAreaWidth, INT_MAX)),
                     pal.color(QPalette::Background));

    QTextBlock block = firstVisibleBlock();
    int blockNumber = block.blockNumber();
    qreal top = blockBoundingGeometry(block).translated(contentOffset()).top();
    qreal bottom = top;

    while (block.isValid() && top <= event->rect().bottom()) {
        top = bottom;
        bottom = top + blockBoundingRect(block).height();
        QTextBlock nextBlock = block.next();

        QTextBlock nextVisibleBlock = nextBlock;
        int nextVisibleBlockNumber = blockNumber + 1;

        if (!nextVisibleBlock.isVisible()) {
            // invisible blocks do have zero line count
            nextVisibleBlock = doc->findBlockByLineNumber(nextVisibleBlock.firstLineNumber());
            nextVisibleBlockNumber = nextVisibleBlock.blockNumber();
        }

        if (bottom < event->rect().top()) {
            block = nextVisibleBlock;
            blockNumber = nextVisibleBlockNumber;
            continue;
        }

        painter.setPen(pal.color(QPalette::Dark));

        // Folding markers
        if (d->codeFoldingVisible || d->marksVisible) {
            painter.save();
            painter.setRenderHint(QPainter::Antialiasing, false);

            int previousBraceDepth = block.previous().userState();
            if (previousBraceDepth >= 0)
                previousBraceDepth >>= 8;
            else
                previousBraceDepth = 0;

            int braceDepth = block.userState();
            if (!nextBlock.isVisible()) {
                QTextBlock lastInvisibleBlock = nextVisibleBlock.previous();
                if (!lastInvisibleBlock.isValid())
                    lastInvisibleBlock = doc->lastBlock();
                braceDepth = lastInvisibleBlock.userState();
            }
            if (braceDepth >= 0)
                braceDepth >>= 8;
            else
                braceDepth = 0;

            if (d->marksVisible) {
                if (TextBlockUserData *userData = static_cast<TextBlockUserData*>(block.userData())) {
                    int xoffset = 0;
                    foreach (ITextMark *mk, userData->marks()) {
                        int x = 0;
                        int radius = fmLineSpacing - 1;
                        QRect r(x + xoffset, top, radius, radius);
                        mk->icon().paint(&painter, r, Qt::AlignCenter);
                        xoffset += 2;
                    }
                }
            }

            if (d->codeFoldingVisible) {
                int extraAreaHighlightFoldBlockNumber = -1;
                int extraAreaHighlightFoldEndBlockNumber = -1;
                bool endIsVisible = false;
                if (!d->highlightBlocksInfo.isEmpty()) {
                    extraAreaHighlightFoldBlockNumber = d->highlightBlocksInfo.open.last();
                    extraAreaHighlightFoldEndBlockNumber = d->highlightBlocksInfo.close.first();
                    endIsVisible = doc->findBlockByNumber(extraAreaHighlightFoldEndBlockNumber).isVisible();
                }

                TextBlockUserData *nextBlockUserData = CodeDocumentLayout::testUserData(nextBlock);

                bool drawBox = nextBlockUserData
                               && CodeDocumentLayout::foldingIndent(block) < nextBlockUserData->foldingIndent();

                bool active = blockNumber == extraAreaHighlightFoldBlockNumber;

                bool drawStart = active;
                bool drawEnd = blockNumber == extraAreaHighlightFoldEndBlockNumber
                               || (drawStart && !endIsVisible);
                bool hovered = blockNumber >= extraAreaHighlightFoldBlockNumber
                               && blockNumber <= extraAreaHighlightFoldEndBlockNumber;

                int boxWidth = foldBoxWidth(fm);
                if (hovered) {
                    int itop = qRound(top);
                    int ibottom = qRound(bottom);
                    QRect box = QRect(extraAreaWidth + 1, itop, boxWidth - 2, ibottom - itop);
                    drawRectBox(&painter, box, drawStart, drawEnd, pal);
                }

                if (drawBox) {
                    bool expanded = nextBlock.isVisible();
                    int size = boxWidth / 4;
                    QRect box(extraAreaWidth + size, top + size,
                              (2 * size) + 1, (2 * size) + 1);
                    drawFoldingMarker(&painter, pal, box, expanded, active, hovered);
                }
            }

            painter.restore();
        }

        // Revision
        if (d->revisionsVisible && block.revision() != docLayout->_lastSaveRevision) {
            painter.save();
            painter.setRenderHint(QPainter::Antialiasing, false);
            if (block.revision() < 0)
                painter.setPen(QPen(Qt::darkGreen, 2));
            else
                painter.setPen(QPen(Qt::red, 2));
            painter.drawLine(extraAreaWidth - 1, top, extraAreaWidth - 1, bottom - 1);
            painter.restore();
        }

        // Line numbers
        if (d->lineNumbersVisible) {
            const QString &number = QString::number(blockNumber + 1);
            bool selected = (selStart < block.position() + block.length()
                             && selEnd > block.position())
                            || (selStart == selEnd && selStart == block.position());
            if (selected) {
                painter.save();
                QFont f = painter.font();
                f.setBold(d->currentLineNumberFormat.font().bold());
                f.setItalic(d->currentLineNumberFormat.font().italic());
                painter.setFont(f);
                painter.setPen(d->currentLineNumberFormat.foreground().color());
            }
            painter.drawText(markWidth, top, extraAreaWidth - markWidth - 4, fm.height(),
                             Qt::AlignRight, number);
            if (selected)
                painter.restore();
        }

        block = nextVisibleBlock;
        blockNumber = nextVisibleBlockNumber;
    }
}

void CodeEditor::drawFoldingMarker(QPainter *painter, const QPalette &pal, const QRect &rect,
                                   bool expanded, bool active, bool hovered) const
{
    Q_UNUSED(active);
    Q_UNUSED(hovered);

    QStyle *s = style();
    QStyleOptionViewItemV2 opt;
    opt.rect = rect;
    opt.state = QStyle::State_Active | QStyle::State_Item | QStyle::State_Children;
    if (expanded)
        opt.state |= QStyle::State_Open;
    if (active)
        opt.state |= QStyle::State_MouseOver | QStyle::State_Enabled | QStyle::State_Selected;
    if (hovered)
        opt.palette.setBrush(QPalette::Window, pal.highlight());

     // QGtkStyle needs a small correction to draw the marker in the right place
    if (!qstrcmp(s->metaObject()->className(), "QGtkStyle"))
       opt.rect.translate(-2, 0);
    else if (!qstrcmp(s->metaObject()->className(), "QMacStyle"))
        opt.rect.translate(-1, 0);

    s->drawPrimitive(QStyle::PE_IndicatorBranch, &opt, painter, this);
}

QRect CodeEditor::foldBox()
{
    if (d->highlightBlocksInfo.isEmpty() || d->extraAreaHighlightFoldedBlockNumber < 0)
        return QRect();

    QTextBlock begin = document()->findBlockByNumber(d->highlightBlocksInfo.open.last());
    QTextBlock end = document()->findBlockByNumber(d->highlightBlocksInfo.close.first());
    if (!begin.isValid() || !end.isValid())
        return QRect();

    QRectF br = blockBoundingGeometry(begin).translated(contentOffset());
    QRectF er = blockBoundingGeometry(end).translated(contentOffset());

    return QRect(d->extraArea->width() - foldBoxWidth(fontMetrics()),
                 int(br.top()),
                 foldBoxWidth(fontMetrics()),
                 er.bottom() - br.top());
}

QTextBlock CodeEditor::foldedBlockAt(const QPoint &pos, QRect *box) const
{
    QPointF offset(contentOffset());
    QTextBlock block = firstVisibleBlock();
    int top = (int)blockBoundingGeometry(block).translated(offset).top();
    int bottom = top + (int)blockBoundingRect(block).height();

    int viewportHeight = viewport()->height();

    while (block.isValid() && top <= viewportHeight) {
        QTextBlock nextBlock = block.next();
        if (block.isVisible() && bottom >= 0) {
            if (nextBlock.isValid() && !nextBlock.isVisible()) {
                QTextLayout *layout = block.layout();
                QTextLine line = layout->lineAt(layout->lineCount()-1);
                QRectF lineRect = line.naturalTextRect().translated(offset.x(), top);
                lineRect.adjust(0, 0, -1, -1);

                QRectF collapseRect(lineRect.right() + 12,
                                    lineRect.top(),
                                    fontMetrics().width(QLatin1String(" {...}; ")),
                                    lineRect.height());
                if (collapseRect.contains(pos)) {
                    QTextBlock result = block;
                    if (box)
                        *box = collapseRect.toAlignedRect();
                    return result;
                } else {
                    block = nextBlock;
                    while (nextBlock.isValid() && !nextBlock.isVisible()) {
                        block = nextBlock;
                        nextBlock = block.next();
                    }
                }
            }
        }

        block = nextBlock;
        top = bottom;
        bottom = top + (int)blockBoundingRect(block).height();
    }
    return QTextBlock();
}
